xiguan / index.html
mulukong's picture
Add 2 files
44eb7ce verified
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>习惯追踪</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
body {
font-family: 'Noto Sans SC', sans-serif;
background-color: #f5f5f5;
max-width: 500px;
margin: 0 auto;
box-shadow: 0 0 20px rgba(0,0,0,0.1);
min-height: 100vh;
position: relative;
}
.habit-card {
transition: all 0.3s ease;
}
.habit-card:hover {
transform: translateY(-2px);
}
.progress-bar {
height: 6px;
border-radius: 3px;
transition: width 0.5s ease;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.5);
z-index: 100;
align-items: center;
justify-content: center;
}
.modal-content {
animation: modalFadeIn 0.3s ease;
}
@keyframes modalFadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.floating-btn {
box-shadow: 0 4px 12px rgba(74, 222, 128, 0.3);
transition: all 0.3s ease;
}
.floating-btn:hover {
transform: scale(1.1);
box-shadow: 0 6px 16px rgba(74, 222, 128, 0.4);
}
.streak-fire {
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
/* 添加动画效果 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.animate-fade-in {
animation: fadeIn 0.3s ease;
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
.animate-fade-out {
animation: fadeOut 0.3s ease;
}
</style>
</head>
<body class="bg-gray-100">
<!-- 顶部栏 -->
<header class="bg-green-500 text-white p-4 shadow-md">
<div class="flex justify-between items-center">
<div>
<h1 class="text-2xl font-bold">习惯追踪</h1>
<p class="text-sm opacity-90">培养好习惯,一天一天来</p>
</div>
<div class="flex items-center space-x-3">
<button id="stats-btn" class="p-2 rounded-full hover:bg-green-600 transition">
<i class="fas fa-chart-line"></i>
</button>
<button id="settings-btn" class="p-2 rounded-full hover:bg-green-600 transition">
<i class="fas fa-cog"></i>
</button>
</div>
</div>
</header>
<!-- 日期导航 -->
<div class="bg-white p-3 flex justify-between items-center shadow-sm">
<button id="prev-day" class="p-2 rounded-full hover:bg-gray-100">
<i class="fas fa-chevron-left"></i>
</button>
<div class="text-center">
<h2 id="current-date" class="font-semibold text-lg">今天</h2>
<p id="current-day" class="text-sm text-gray-500">星期一, 6月5日</p>
</div>
<button id="next-day" class="p-2 rounded-full hover:bg-gray-100">
<i class="fas fa-chevron-right"></i>
</button>
</div>
<!-- 习惯列表 -->
<main class="p-4 space-y-4">
<div class="flex justify-between items-center">
<h3 class="font-medium text-gray-700">今日习惯</h3>
<span id="completion-rate" class="text-sm font-medium text-green-500">0% 完成</span>
</div>
<div id="habits-container" class="space-y-3">
<!-- 习惯卡片将在这里动态添加 -->
</div>
<div id="empty-state" class="text-center py-10">
<i class="fas fa-clipboard-list text-4xl text-gray-300 mb-3"></i>
<h4 class="text-gray-500 font-medium">还没有习惯</h4>
<p class="text-gray-400 text-sm mt-1">添加你的第一个习惯开始追踪</p>
<button id="add-first-habit" class="mt-4 bg-green-500 text-white px-4 py-2 rounded-full font-medium hover:bg-green-600 transition">
添加习惯
</button>
</div>
</main>
<!-- 悬浮按钮 -->
<button id="add-habit-btn" class="fixed bottom-6 right-6 bg-green-500 text-white p-4 rounded-full shadow-lg floating-btn">
<i class="fas fa-plus text-xl"></i>
</button>
<!-- 添加习惯弹窗 -->
<div id="add-habit-modal" class="modal">
<div class="modal-content bg-white rounded-lg w-full max-w-md mx-4">
<div class="p-5">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold text-gray-800">添加新习惯</h3>
<button id="close-modal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">习惯名称</label>
<input type="text" id="habit-name" placeholder="例如:晨跑" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">图标</label>
<div class="grid grid-cols-6 gap-2">
<button class="habit-icon p-3 rounded-lg border hover:bg-gray-100" data-icon="dumbbell">
<i class="fas fa-dumbbell"></i>
</button>
<button class="habit-icon p-3 rounded-lg border hover:bg-gray-100" data-icon="book">
<i class="fas fa-book"></i>
</button>
<button class="habit-icon p-3 rounded-lg border hover:bg-gray-100" data-icon="running">
<i class="fas fa-running"></i>
</button>
<button class="habit-icon p-3 rounded-lg border hover:bg-gray-100" data-icon="bed">
<i class="fas fa-bed"></i>
</button>
<button class="habit-icon p-3 rounded-lg border hover:bg-gray-100" data-icon="apple-alt">
<i class="fas fa-apple-alt"></i>
</button>
<button class="habit-icon p-3 rounded-lg border hover:bg-gray-100" data-icon="water">
<i class="fas fa-tint"></i>
</button>
<button class="habit-icon p-3 rounded-lg border hover:bg-gray-100" data-icon="meditation">
<i class="fas fa-spa"></i>
</button>
<button class="habit-icon p-3 rounded-lg border hover:bg-gray-100" data-icon="code">
<i class="fas fa-code"></i>
</button>
<button class="habit-icon p-3 rounded-lg border hover:bg-gray-100" data-icon="music">
<i class="fas fa-music"></i>
</button>
<button class="habit-icon p-3 rounded-lg border hover:bg-gray-100" data-icon="journal">
<i class="fas fa-book-open"></i>
</button>
<button class="habit-icon p-3 rounded-lg border hover:bg-gray-100" data-icon="pray">
<i class="fas fa-hands-praying"></i>
</button>
<button class="habit-icon p-3 rounded-lg border hover:bg-gray-100" data-icon="custom">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">频率</label>
<select id="habit-frequency" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500">
<option value="daily">每日</option>
<option value="weekly">每周</option>
<option value="monthly">每月</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">提醒</label>
<div class="flex items-center space-x-2">
<input type="time" id="habit-reminder" class="flex-1 p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500">
<button id="toggle-reminder" class="p-3 bg-gray-200 rounded-lg">
<i class="fas fa-bell-slash text-gray-500"></i>
</button>
</div>
</div>
<div class="pt-2">
<button id="save-habit" class="w-full bg-green-500 text-white py-3 rounded-lg font-medium hover:bg-green-600 transition">
保存习惯
</button>
</div>
</div>
</div>
</div>
</div>
<!-- 统计弹窗 -->
<div id="stats-modal" class="modal">
<div class="modal-content bg-white rounded-lg w-full max-w-md mx-4">
<div class="p-5">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold text-gray-800">你的进度</h3>
<button id="close-stats" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="space-y-6">
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="font-medium text-gray-700 mb-2">完成率</h4>
<div class="flex items-center space-x-3">
<div class="w-full bg-gray-200 rounded-full h-4">
<div id="overall-progress" class="bg-green-500 h-4 rounded-full" style="width: 0%"></div>
</div>
<span id="overall-percentage" class="text-sm font-medium text-gray-600">0%</span>
</div>
</div>
<div>
<h4 class="font-medium text-gray-700 mb-3">当前连续记录</h4>
<div id="streaks-container" class="space-y-3">
<!-- 连续记录将在这里添加 -->
</div>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="font-medium text-gray-700 mb-2">习惯详情</h4>
<div id="habits-breakdown" class="space-y-2">
<!-- 详情将在这里添加 -->
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// 当DOM加载完成后初始化应用
document.addEventListener('DOMContentLoaded', function() {
// 设置当前日期
updateCurrentDate();
// 检查localStorage中是否有习惯
const habits = JSON.parse(localStorage.getItem('habits')) || [];
// 如果有习惯,隐藏空状态并显示习惯
if (habits.length > 0) {
document.getElementById('empty-state').style.display = 'none';
renderHabits(habits);
updateCompletionRate(habits);
}
// 按钮事件监听
document.getElementById('add-habit-btn').addEventListener('click', openAddHabitModal);
document.getElementById('add-first-habit').addEventListener('click', openAddHabitModal);
document.getElementById('close-modal').addEventListener('click', closeAddHabitModal);
document.getElementById('save-habit').addEventListener('click', saveHabit);
document.getElementById('stats-btn').addEventListener('click', openStatsModal);
document.getElementById('close-stats').addEventListener('click', closeStatsModal);
document.getElementById('prev-day').addEventListener('click', navigateToPreviousDay);
document.getElementById('next-day').addEventListener('click', navigateToNextDay);
document.getElementById('toggle-reminder').addEventListener('click', toggleReminder);
// 为所有习惯图标添加事件监听
const habitIcons = document.querySelectorAll('.habit-icon');
habitIcons.forEach(icon => {
icon.addEventListener('click', function() {
// 从所有图标中移除active类
habitIcons.forEach(i => i.classList.remove('bg-green-100', 'border-green-500'));
// 为点击的图标添加active类
this.classList.add('bg-green-100', 'border-green-500');
// 存储选中的图标
this.dataset.selected = 'true';
});
});
});
// 当前日期跟踪
let currentDisplayDate = new Date();
// 更新当前日期显示
function updateCurrentDate() {
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
const today = new Date();
if (currentDisplayDate.toDateString() === today.toDateString()) {
document.getElementById('current-date').textContent = '今天';
} else {
document.getElementById('current-date').textContent = currentDisplayDate.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
}
document.getElementById('current-day').textContent = currentDisplayDate.toLocaleDateString('zh-CN', options);
}
// 导航到前一天
function navigateToPreviousDay() {
currentDisplayDate.setDate(currentDisplayDate.getDate() - 1);
updateCurrentDate();
// 在实际应用中,你会更新这个日期的习惯显示
}
// 导航到后一天
function navigateToNextDay() {
const today = new Date();
if (currentDisplayDate.toDateString() === today.toDateString()) return;
currentDisplayDate.setDate(currentDisplayDate.getDate() + 1);
updateCurrentDate();
// 在实际应用中,你会更新这个日期的习惯显示
}
// 打开添加习惯弹窗
function openAddHabitModal() {
document.getElementById('add-habit-modal').style.display = 'flex';
}
// 关闭添加习惯弹窗
function closeAddHabitModal() {
document.getElementById('add-habit-modal').style.display = 'none';
// 重置表单
document.getElementById('habit-name').value = '';
document.getElementById('habit-frequency').value = 'daily';
document.getElementById('habit-reminder').value = '';
// 重置选中的图标
const habitIcons = document.querySelectorAll('.habit-icon');
habitIcons.forEach(icon => {
icon.classList.remove('bg-green-100', 'border-green-500');
delete icon.dataset.selected;
});
}
// 切换提醒
function toggleReminder() {
const btn = document.getElementById('toggle-reminder');
const icon = btn.querySelector('i');
if (icon.classList.contains('fa-bell-slash')) {
icon.classList.remove('fa-bell-slash', 'text-gray-500');
icon.classList.add('fa-bell', 'text-green-500');
} else {
icon.classList.remove('fa-bell', 'text-green-500');
icon.classList.add('fa-bell-slash', 'text-gray-500');
}
}
// 保存新习惯
function saveHabit() {
const name = document.getElementById('habit-name').value.trim();
if (!name) {
alert('请输入习惯名称');
return;
}
// 获取选中的图标
const selectedIcon = document.querySelector('.habit-icon[data-selected="true"]');
const icon = selectedIcon ? selectedIcon.dataset.icon : 'plus';
const frequency = document.getElementById('habit-frequency').value;
// 检查是否启用了提醒
const reminderBtn = document.getElementById('toggle-reminder');
const reminderEnabled = !reminderBtn.querySelector('i').classList.contains('fa-bell-slash');
const reminderTime = reminderEnabled ? document.getElementById('habit-reminder').value : null;
// 创建新习惯对象
const newHabit = {
id: Date.now(),
name,
icon,
frequency,
reminder: reminderTime,
completedDates: [],
streak: 0,
bestStreak: 0
};
// 从localStorage获取现有习惯
const habits = JSON.parse(localStorage.getItem('habits')) || [];
habits.push(newHabit);
// 保存到localStorage
localStorage.setItem('habits', JSON.stringify(habits));
// 更新UI
document.getElementById('empty-state').style.display = 'none';
renderHabits(habits);
updateCompletionRate(habits);
closeAddHabitModal();
// 显示成功消息
showToast('习惯添加成功!');
}
// 渲染习惯列表
function renderHabits(habits) {
const container = document.getElementById('habits-container');
container.innerHTML = '';
habits.forEach(habit => {
const today = new Date().toDateString();
const isCompleted = habit.completedDates.includes(today);
const habitCard = document.createElement('div');
habitCard.className = 'habit-card bg-white p-4 rounded-lg shadow-sm border-l-4 border-green-500';
habitCard.dataset.id = habit.id;
habitCard.innerHTML = `
<div class="flex justify-between items-start">
<div class="flex items-center space-x-3">
<div class="bg-green-100 p-3 rounded-full text-green-600">
<i class="fas fa-${habit.icon}"></i>
</div>
<div>
<h4 class="font-medium">${habit.name}</h4>
<p class="text-xs text-gray-500">${habit.frequency === 'daily' ? '每日' : habit.frequency === 'weekly' ? '每周' : '每月'}</p>
</div>
</div>
<div class="flex items-center space-x-2">
<span class="text-xs font-medium ${habit.streak > 0 ? 'text-orange-500' : 'text-gray-400'}">
${habit.streak > 0 ? `${habit.streak} 天连续` : '暂无连续记录'}
</span>
${habit.streak > 3 ? '<i class="fas fa-fire text-orange-500 streak-fire"></i>' : ''}
</div>
</div>
<div class="mt-3 flex justify-between items-center">
<div class="w-full bg-gray-200 rounded-full h-1.5 mr-2">
<div class="progress-bar bg-green-500 h-1.5 rounded-full" style="width: ${(habit.completedDates.length / 30) * 100}%"></div>
</div>
<button class="complete-btn p-2 rounded-full ${isCompleted ? 'bg-green-500 text-white' : 'bg-gray-100 text-gray-500 hover:bg-green-100 hover:text-green-500'}">
<i class="fas fa-check"></i>
</button>
</div>
`;
container.appendChild(habitCard);
// 为完成按钮添加事件监听
const completeBtn = habitCard.querySelector('.complete-btn');
completeBtn.addEventListener('click', function() {
toggleHabitCompletion(habit.id);
});
});
}
// 切换习惯完成状态
function toggleHabitCompletion(habitId) {
const habits = JSON.parse(localStorage.getItem('habits')) || [];
const habitIndex = habits.findIndex(h => h.id === habitId);
if (habitIndex === -1) return;
const today = new Date().toDateString();
const completedDates = habits[habitIndex].completedDates;
const isCompleted = completedDates.includes(today);
if (isCompleted) {
// 从完成日期中移除
habits[habitIndex].completedDates = completedDates.filter(d => d !== today);
// 如果是昨天完成的,减少连续记录
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
if (completedDates.includes(yesterday.toDateString())) {
habits[habitIndex].streak = Math.max(0, habits[habitIndex].streak - 1);
}
} else {
// 添加到完成日期
habits[habitIndex].completedDates.push(today);
// 如果是昨天完成的或没有完成记录,增加连续记录
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
if (completedDates.includes(yesterday.toDateString()) || completedDates.length === 0) {
habits[habitIndex].streak += 1;
if (habits[habitIndex].streak > habits[habitIndex].bestStreak) {
habits[habitIndex].bestStreak = habits[habitIndex].streak;
}
} else {
// 如果不是连续的,重置连续记录
habits[habitIndex].streak = 1;
}
}
// 保存到localStorage
localStorage.setItem('habits', JSON.stringify(habits));
// 更新UI
renderHabits(habits);
updateCompletionRate(habits);
// 显示提示消息
showToast(isCompleted ? '习惯标记为未完成' : '习惯已完成!做得好!');
}
// 更新完成率
function updateCompletionRate(habits) {
if (habits.length === 0) {
document.getElementById('completion-rate').textContent = '0% 完成';
return;
}
const today = new Date().toDateString();
const completedCount = habits.filter(h => h.completedDates.includes(today)).length;
const completionRate = Math.round((completedCount / habits.length) * 100);
document.getElementById('completion-rate').textContent = `${completionRate}% 完成`;
}
// 打开统计弹窗
function openStatsModal() {
const habits = JSON.parse(localStorage.getItem('habits')) || [];
if (habits.length === 0) {
showToast('没有习惯可以显示统计');
return;
}
// 计算总体完成率
const totalPossibleCompletions = habits.length * 30; // 假设30天
let totalCompletions = 0;
habits.forEach(habit => {
totalCompletions += habit.completedDates.length;
});
const overallCompletionRate = Math.round((totalCompletions / totalPossibleCompletions) * 100);
// 更新总体进度
document.getElementById('overall-progress').style.width = `${overallCompletionRate}%`;
document.getElementById('overall-percentage').textContent = `${overallCompletionRate}%`;
// 更新连续记录
const streaksContainer = document.getElementById('streaks-container');
streaksContainer.innerHTML = '';
habits.forEach(habit => {
const streakItem = document.createElement('div');
streakItem.className = 'flex justify-between items-center bg-gray-50 p-3 rounded-lg';
streakItem.innerHTML = `
<div class="flex items-center space-x-3">
<div class="bg-green-100 p-2 rounded-full text-green-600">
<i class="fas fa-${habit.icon}"></i>
</div>
<span class="font-medium">${habit.name}</span>
</div>
<div class="flex items-center space-x-2">
<span class="text-sm ${habit.streak > 0 ? 'text-orange-500 font-medium' : 'text-gray-500'}">
${habit.streak}${habit.streak !== 1 ? '' : ''}
</span>
${habit.streak > 3 ? '<i class="fas fa-fire text-orange-500 streak-fire"></i>' : ''}
</div>
`;
streaksContainer.appendChild(streakItem);
});
// 更新习惯详情
const breakdownContainer = document.getElementById('habits-breakdown');
breakdownContainer.innerHTML = '';
habits.forEach(habit => {
const completionRate = Math.round((habit.completedDates.length / 30) * 100);
const breakdownItem = document.createElement('div');
breakdownItem.className = 'space-y-1';
breakdownItem.innerHTML = `
<div class="flex justify-between text-sm">
<span>${habit.name}</span>
<span class="font-medium">${completionRate}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-1.5">
<div class="bg-green-500 h-1.5 rounded-full" style="width: ${completionRate}%"></div>
</div>
`;
breakdownContainer.appendChild(breakdownItem);
});
document.getElementById('stats-modal').style.display = 'flex';
}
// 关闭统计弹窗
function closeStatsModal() {
document.getElementById('stats-modal').style.display = 'none';
}
// 显示提示消息
function showToast(message) {
const toast = document.createElement('div');
toast.className = 'fixed bottom-6 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white px-4 py-2 rounded-lg shadow-lg text-sm animate-fade-in';
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.classList.add('animate-fade-out');
setTimeout(() => {
toast.remove();
}, 300);
}, 3000);
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=mulukong/xiguan" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>