【html】每日打卡页面

前言

这是一个基于HTML/CSS/JavaScript开发的每日打卡网页应用,由单个html文件构成,5种配色主题可选,响应式设计,采用本地存储记录数据。

项目于2025年8月份借助DeepSeek完成。

功能说明

1.基本功能

在页面中,用户可以添加、删除、清空打卡事项,还可以拖动排序。当用户单击某个事项卡片时,该卡片会变色以表示该项已完成,再次单击则会恢复。

2.扩展功能

(1)可以在页面上切换配色方案。目前有五套配色方案,可以在JavaScript中方便地切换,后续在CSS中添加新方案也很方便。

(2)采用响应式设计,在大屏幕上默认双列展示,在小屏幕上自动单列展示。

(3)采用本地存储记录数据,只要在同一台电脑的同一个浏览器上,就能保存打卡数据。

使用方式

考虑到便携性,我把html、CSS、JavaScript部分都整合在一个html文件中。

因此,复制代码到html文件中,双击打开即用。

在我的电脑(Win10,Edge浏览器)上测试通过。

备注:这是借助DeepSeek写的,代码细节我没有做过多研究,如有不当之处,敬请指正。

完整代码

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>
        /* CSS变量定义 - 支持多主题 */
        :root {
            /* 默认主题 - 蓝色系 */
            --primary-color: #4a6fa5;
            --secondary-color: #6e9cd2;
            --background-color: #f5f7fa;
            --card-color: #ffffff;
            --text-color: #333333;
            --completed-color: #e8f5e9;
            --border-color: #e0e0e0;
            --shadow-color: rgba(0, 0, 0, 0.1);
            --danger-color: #f44336;
            --success-color: #4caf50;
        }

        /* 绿色主题 */
        [data-theme="green"] {
            --primary-color: #4caf50;
            --secondary-color: #81c784;
            --background-color: #f1f8e9;
            --card-color: #ffffff;
            --text-color: #33691e;
            --completed-color: #e8f5e9;
            --border-color: #c5e1a5;
            --shadow-color: rgba(76, 175, 80, 0.2);
        }

        /* 紫色主题 */
        [data-theme="purple"] {
            --primary-color: #7e57c2;
            --secondary-color: #b39ddb;
            --background-color: #f3e5f5;
            --card-color: #ffffff;
            --text-color: #4527a0;
            --completed-color: #ede7f6;
            --border-color: #d1c4e9;
            --shadow-color: rgba(126, 87, 194, 0.2);
        }

        /* 橙色主题 */
        [data-theme="orange"] {
            --primary-color: #ff9800;
            --secondary-color: #ffb74d;
            --background-color: #fff3e0;
            --card-color: #ffffff;
            --text-color: #e65100;
            --completed-color: #ffe0b2;
            --border-color: #ffcc80;
            --shadow-color: rgba(255, 152, 0, 0.2);
        }

        /* 深色主题 */
        [data-theme="dark"] {
            --primary-color: #9c27b0;
            --secondary-color: #ba68c8;
            --background-color: #303030;
            --card-color: #424242;
            --text-color: #f5f5f5;
            --completed-color: #6a1b9a;
            --border-color: #616161;
            --shadow-color: rgba(0, 0, 0, 0.4);
        }

        /* 基础样式 */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Helvetica Neue', Arial, sans-serif;
            background-color: var(--background-color);
            color: var(--text-color);
            line-height: 1.6;
            padding: 20px;
            transition: background-color 0.3s, color 0.3s;
        }

        .container {
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }

        /* 头部样式 */
        header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 30px;
            flex-wrap: wrap;
            gap: 15px;
        }

        h1 {
            color: var(--primary-color);
            font-size: 2.2rem;
            margin: 0;
        }

        /* 控制按钮组样式 */
        .controls {
            display: flex;
            gap: 10px;
        }

        button {
            padding: 10px 15px;
            border: none;
            border-radius: 5px;
            background-color: var(--primary-color);
            color: white;
            cursor: pointer;
            font-size: 0.9rem;
            transition: background-color 0.2s, transform 0.1s;
        }

        button:hover {
            background-color: var(--secondary-color);
        }

        button:active {
            transform: scale(0.98);
        }

        button.danger {
            background-color: var(--danger-color);
        }

        button.danger:hover {
            background-color: #e53935;
        }

        /* 主题选择器样式 */
        .theme-selector {
            position: relative;
            display: inline-block;
        }

        .theme-selector select {
            padding: 10px 15px;
            border: 1px solid var(--border-color);
            border-radius: 5px;
            background-color: var(--card-color);
            color: var(--text-color);
            appearance: none;
            cursor: pointer;
            width: 150px;
        }

        /* 打卡项目列表样式 */
        .habits-list {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
            gap: 20px;
            margin-bottom: 40px;
        }

        /* 打卡项目卡片样式 */
        .habit-card {
            background-color: var(--card-color);
            border-radius: 10px;
            padding: 20px;
            box-shadow: 0 4px 8px var(--shadow-color);
            transition: transform 0.2s, box-shadow 0.2s, background-color 0.3s;
            cursor: pointer;
            position: relative;
            user-select: none;
        }

        .habit-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 6px 12px var(--shadow-color);
        }

        .habit-card.completed {
            background-color: var(--completed-color);
        }

        .habit-card .habit-name {
            font-size: 1.2rem;
            margin-bottom: 10px;
            font-weight: 500;
        }

        .habit-card .delete-btn {
            position: absolute;
            top: 10px;
            right: 10px;
            width: 25px;
            height: 25px;
            border-radius: 50%;
            background-color: rgba(244, 67, 54, 0.2);
            color: var(--danger-color);
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 0.8rem;
            opacity: 0;
            transition: opacity 0.2s;
        }

        .habit-card:hover .delete-btn {
            opacity: 1;
        }

        .habit-card .delete-btn:hover {
            background-color: var(--danger-color);
            color: white;
        }

        /* 添加新项目表单样式 */
        .add-habit-form {
            background-color: var(--card-color);
            border-radius: 10px;
            padding: 20px;
            box-shadow: 0 4px 8px var(--shadow-color);
            margin-bottom: 30px;
        }

        .add-habit-form h2 {
            margin-bottom: 15px;
            color: var(--primary-color);
            font-size: 1.5rem;
        }

        .form-group {
            display: flex;
            gap: 10px;
        }

        .form-group input {
            flex: 1;
            padding: 10px 15px;
            border: 1px solid var(--border-color);
            border-radius: 5px;
            font-size: 1rem;
            background-color: var(--card-color);
            color: var(--text-color);
        }

        /* 响应式设计 */
        @media (max-width: 600px) {
            header {
                flex-direction: column;
                align-items: stretch;
                text-align:center;
            }
            
            .controls {
                justify-content: center;
            }
            
            .habits-list {
                grid-template-columns: 1fr;
            }
            
            .form-group {
                flex-direction: column;
            }
        }

        /* 拖动时的样式 */
        .habit-card.dragging {
            opacity: 0.5;
            box-shadow: 0 10px 20px var(--shadow-color);
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>每日打卡</h1>
            <div class="controls">
                <div class="theme-selector">
                    <select id="theme-switcher">
                        <option value="default">默认主题</option>
                        <option value="green">绿色主题</option>
                        <option value="purple">紫色主题</option>
                        <option value="orange">橙色主题</option>
                        <option value="dark">深色主题</option>
                    </select>
                </div>
                <button id="reset-btn" class="danger">清空今日打卡</button>
            </div>
        </header>

        <main>
            <div class="add-habit-form">
                <h2>添加新项目</h2>
                <div class="form-group">
                    <input type="text" id="new-habit-input" placeholder="输入打卡项目名称">
                    <button id="add-habit-btn">添加</button>
                </div>
            </div>

            <div class="habits-list" id="habits-container">
                <!-- 打卡项目将通过JavaScript动态添加 -->
            </div>
        </main>

    </div>

    <script>
        // 定义全局变量
        let habits = []; // 存储所有打卡项目
        let currentDate = new Date().toDateString(); // 当前日期,用于判断是否是新的一天

        /**
         * 初始化应用
         * 从localStorage加载数据并设置事件监听器
         */
        function initApp() {
            // 从localStorage加载数据
            loadFromLocalStorage();
            
            // 检查是否是新的一天,如果是则重置打卡状态
            checkForNewDay();
            
            // 渲染打卡项目
            renderHabits();
            
            // 设置事件监听器
            setupEventListeners();
        }

        /**
         * 从localStorage加载数据
         */
        function loadFromLocalStorage() {
            // 尝试从localStorage加载打卡项目
            const savedHabits = localStorage.getItem('dailyHabits');
            if (savedHabits) {
                habits = JSON.parse(savedHabits);
            } else {
                // 如果没有保存的数据,使用默认项目
                habits = [
                    { id: 1, name: '背单词', completed: false },
                    { id: 2, name: '吃早饭', completed: false },
                    { id: 3, name: '运动30分钟', completed: false }
                ];
                saveToLocalStorage();
            }
            
            // 加载上次使用的主题
            const savedTheme = localStorage.getItem('selectedTheme') || 'default';
            document.documentElement.setAttribute('data-theme', savedTheme);
            document.getElementById('theme-switcher').value = savedTheme;
            
            // 加载上次使用的日期
            const savedDate = localStorage.getItem('lastAccessDate');
            if (savedDate) {
                // 不需要做任何操作,我们已经在checkForNewDay中处理了
            }
        }

        /**
         * 检查是否是新的一天,如果是则重置所有打卡状态
         */
        function checkForNewDay() {
            const lastAccessDate = localStorage.getItem('lastAccessDate');
            
            if (lastAccessDate !== currentDate) {
                // 是新的一天,重置所有打卡状态
                habits.forEach(habit => {
                    habit.completed = false;
                });
                saveToLocalStorage();
                
                // 更新最后访问日期
                localStorage.setItem('lastAccessDate', currentDate);
            }
        }

        /**
         * 保存数据到localStorage
         */
        function saveToLocalStorage() {
            localStorage.setItem('dailyHabits', JSON.stringify(habits));
        }

        /**
         * 渲染所有打卡项目
         */
        function renderHabits() {
            const container = document.getElementById('habits-container');
            container.innerHTML = ''; // 清空容器
            
            habits.forEach(habit => {
                const habitElement = createHabitElement(habit);
                container.appendChild(habitElement);
            });
        }

        /**
         * 创建单个打卡项目的DOM元素
         * @param {Object} habit 打卡项目对象
         * @returns {HTMLElement} 打卡项目的DOM元素
         */
        function createHabitElement(habit) {
            const habitCard = document.createElement('div');
            habitCard.className = `habit-card ${habit.completed ? 'completed' : ''}`;
            habitCard.setAttribute('data-id', habit.id);
            habitCard.draggable = true;
            
            const habitName = document.createElement('div');
            habitName.className = 'habit-name';
            habitName.textContent = habit.name;
            
            const deleteBtn = document.createElement('div');
            deleteBtn.className = 'delete-btn';
            deleteBtn.innerHTML = '×';
            deleteBtn.addEventListener('click', (e) => {
                e.stopPropagation(); // 阻止事件冒泡,避免触发打卡操作
                deleteHabit(habit.id);
            });
            
            habitCard.appendChild(habitName);
            habitCard.appendChild(deleteBtn);
            
            // 添加点击事件(打卡/取消打卡)
            habitCard.addEventListener('click', () => {
                toggleHabitCompletion(habit.id);
            });
            
            // 添加拖拽事件
            habitCard.addEventListener('dragstart', handleDragStart);
            habitCard.addEventListener('dragover', handleDragOver);
            habitCard.addEventListener('drop', handleDrop);
            habitCard.addEventListener('dragend', handleDragEnd);
            
            return habitCard;
        }

        /**
         * 切换打卡项目的完成状态
         * @param {number} id 打卡项目的ID
         */
        function toggleHabitCompletion(id) {
            const habit = habits.find(h => h.id === id);
            if (habit) {
                habit.completed = !habit.completed;
                saveToLocalStorage();
                renderHabits();
            }
        }

        /**
         * 删除打卡项目
         * @param {number} id 要删除的打卡项目的ID
         */
        function deleteHabit(id) {
            if (confirm('确定要删除这个打卡项目吗?')) {
                habits = habits.filter(habit => habit.id !== id);
                saveToLocalStorage();
                renderHabits();
            }
        }

        /**
         * 添加新的打卡项目
         */
        function addNewHabit() {
            const input = document.getElementById('new-habit-input');
            const name = input.value.trim();
            
            if (name) {
                // 创建新项目
                const newHabit = {
                    id: Date.now(), // 使用时间戳作为唯一ID
                    name: name,
                    completed: false
                };
                
                habits.push(newHabit);
                saveToLocalStorage();
                renderHabits();
                
                // 清空输入框
                input.value = '';
            } else {
                alert('请输入打卡项目名称');
            }
        }

        /**
         * 清空所有打卡状态(用于新的一天)
         */
        function resetAllHabits() {
            if (confirm('确定要清空今日打卡状态吗?')) {
                habits.forEach(habit => {
                    habit.completed = false;
                });
                saveToLocalStorage();
                renderHabits();
            }
        }

        /**
         * 切换主题
         * @param {string} theme 主题名称
         */
        function switchTheme(theme) {
            document.documentElement.setAttribute('data-theme', theme);
            localStorage.setItem('selectedTheme', theme);
        }

        // 拖拽相关变量
        let draggedItem = null;

        /**
         * 处理拖拽开始事件
         * @param {DragEvent} e 拖拽事件对象
         */
        function handleDragStart(e) {
            draggedItem = this;
            this.classList.add('dragging');
            e.dataTransfer.effectAllowed = 'move';
            e.dataTransfer.setData('text/plain', this.getAttribute('data-id'));
        }

        /**
         * 处理拖拽结束事件
         */
        function handleDragEnd() {
            this.classList.remove('dragging');
            draggedItem = null;
        }

        /**
         * 处理拖拽经过事件
         * @param {DragEvent} e 拖拽事件对象
         */
        function handleDragOver(e) {
            e.preventDefault();
            return false;
        }

        /**
         * 处理放置事件
         * @param {DragEvent} e 拖拽事件对象
         */
        function handleDrop(e) {
            e.preventDefault();
            e.stopPropagation();
            
            if (draggedItem !== this) {
                // 获取拖拽项目和目标项目的ID
                const draggedId = parseInt(draggedItem.getAttribute('data-id'));
                const targetId = parseInt(this.getAttribute('data-id'));
                
                // 找到两个项目在数组中的索引
                const draggedIndex = habits.findIndex(h => h.id === draggedId);
                const targetIndex = habits.findIndex(h => h.id === targetId);
                
                if (draggedIndex !== -1 && targetIndex !== -1) {
                    // 重新排序数组
                    const [draggedHabit] = habits.splice(draggedIndex, 1);
                    habits.splice(targetIndex, 0, draggedHabit);
                    
                    // 保存并重新渲染
                    saveToLocalStorage();
                    renderHabits();
                }
            }
            
            return false;
        }

        /**
         * 设置所有事件监听器
         */
        function setupEventListeners() {
            // 添加新项目按钮点击事件
            document.getElementById('add-habit-btn').addEventListener('click', addNewHabit);
            
            // 输入框回车键事件
            document.getElementById('new-habit-input').addEventListener('keypress', (e) => {
                if (e.key === 'Enter') {
                    addNewHabit();
                }
            });
            
            // 主题切换事件
            document.getElementById('theme-switcher').addEventListener('change', (e) => {
                switchTheme(e.target.value);
            });
            
            // 清空打卡按钮点击事件
            document.getElementById('reset-btn').addEventListener('click', resetAllHabits);
        }

        // 当DOM加载完成后初始化应用
        document.addEventListener('DOMContentLoaded', initApp);
    </script>
</body>
</html>
相关推荐
Monly213 小时前
HTML:Video视频切换时不重新加载
1024程序员节
兜兜风d'3 小时前
RabbitMQ 高级特性:消息确认机制详解
spring boot·分布式·rabbitmq·java-rabbitmq·1024程序员节
元直数字电路验证3 小时前
HTML 标签及推荐嵌套结构
前端·javascript·html
charlie1145141913 小时前
HTML 理论笔记
开发语言·前端·笔记·学习·html·1024程序员节
蓝莓味的口香糖3 小时前
【前端】前端浏览器性能优化的小方法
1024程序员节
周杰伦_Jay3 小时前
【常用设计模式全解析】创建型模式(聚焦对象创建机制)、结构型模式(优化类与对象的组合关系)、行为型模式(规范对象间的交互行为)
设计模式·架构·开源·交互·1024程序员节
More more4 小时前
uniapp小程序实现手动向上滑动窗口
1024程序员节
isNotNullX4 小时前
一文讲清:数据清洗、数据中台、数据仓库、数据治理
大数据·网络·数据库·数据分析·1024程序员节
海鸥两三4 小时前
Uni-App(Vue3 + TypeScript)项目结构详解 ------ 以 Lighting-UniApp 为例,提供源代码
vue.js·typescript·uni-app·1024程序员节