【html】每日打卡页面
前言
这是一个基于HTML/CSS/JavaScript开发的每日打卡网页应用,由单个html文件构成,5种配色主题可选,响应式设计,采用本地存储记录数据。
项目于2025年8月份借助DeepSeek完成。

功能说明
1.基本功能
在页面中,用户可以添加、删除、清空打卡事项,还可以拖动排序。当用户单击某个事项卡片时,该卡片会变色以表示该项已完成,再次单击则会恢复。
2.扩展功能
(1)可以在页面上切换配色方案。目前有五套配色方案,可以在JavaScript中方便地切换,后续在CSS中添加新方案也很方便。
(2)采用响应式设计,在大屏幕上默认双列展示,在小屏幕上自动单列展示。
(3)采用本地存储记录数据,只要在同一台电脑的同一个浏览器上,就能保存打卡数据。
使用方式
考虑到便携性,我把html、CSS、JavaScript部分都整合在一个html文件中。
因此,复制代码到html文件中,双击打开即用。
在我的电脑(Win10,Edge浏览器)上测试通过。
备注:这是借助DeepSeek写的,代码细节我没有做过多研究,如有不当之处,敬请指正。
完整代码
<!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(), // 使用时间戳作为唯一IDname: 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) {// 获取拖拽项目和目标项目的IDconst 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>
