forks

Creation chat

ClarkZhang
支持中文
02-25 11:45

登录 后可以评论

Assistant
(HTML code has been generated and updated; see above for the current version.)
▼ Code
<!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>
    <script>
        tailwind.config = {
            darkMode: 'class',
            theme: {
                extend: {
                    colors: {
                        primary: {
                            50: '#eff6ff',
                            100: '#dbeafe',
                            200: '#bfdbfe',
                            300: '#93c5fd',
                            400: '#60a5fa',
                            500: '#3b82f6',
                            600: '#2563eb',
                            700: '#1d4ed8',
                            800: '#1e40af',
                            900: '#1e3a8a',
                        }
                    }
                }
            }
        }
    </script>
    <style>
        .calendar-day.today {
            background-color: #dbeafe;
        }
        .dark .calendar-day.today {
            background-color: #1e3a8a;
        }
        .calendar-day.period {
            background-color: #fecaca;
        }
        .dark .calendar-day.period {
            background-color: #7f1d1d;
        }
        .calendar-day.current-period {
            background-color: #f87171;
            color: white;
        }
        .dark .calendar-day.current-period {
            background-color: #b91c1c;
        }
        .calendar-day.predicted-period {
            background-color: #fed7d7;
        }
        .dark .calendar-day.predicted-period {
            background-color: #7f1d1d;
        }
    </style>
</head>
<body class="min-h-screen bg-slate-50 dark:bg-slate-900 text-slate-900 dark:text-slate-100">
    <!-- 主题切换按钮 -->
    <button id="theme-toggle-btn" class="fixed top-4 right-4 z-10 p-2 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 transition-colors flex items-center justify-center" aria-label="切换主题" onclick="toggleTheme()">
        <svg id="icon-moon" class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998z"/></svg>
        <svg id="icon-sun" class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true" style="display:none"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"></path></svg>
    </button>
    <script>
        function toggleTheme(){ 
            var el=document.documentElement; 
            var isDark=!el.classList.contains('dark'); 
            el.classList.toggle('dark',isDark); 
            localStorage.setItem('theme',isDark?'dark':'light'); 
            applyTheme(isDark); 
        }
        function applyTheme(isDark){ 
            document.getElementById('icon-moon').style.display=isDark?'none':'block'; 
            document.getElementById('icon-sun').style.display=isDark?'block':'none'; 
        }
        document.addEventListener('DOMContentLoaded',function(){ 
            var saved=localStorage.getItem('theme'); 
            var isDark=saved==='dark'||(!saved&&window.matchMedia('(prefers-color-scheme: dark)').matches); 
            document.documentElement.classList.toggle('dark',isDark); 
            applyTheme(isDark); 
        });
    </script>

    <!-- 自定义弹窗 -->
    <div id="custom-modal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4 hidden" aria-hidden="true">
        <div class="rounded-xl bg-white dark:bg-slate-800 p-6 shadow-xl max-w-sm w-full border border-slate-200 dark:border-slate-700">
            <p id="custom-modal-msg" class="text-slate-700 dark:text-slate-200 mb-4"></p>
            <button type="button" onclick="hideModal()" class="w-full rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600">确定</button>
        </div>
    </div>
    <script>
        function showModal(msg){ 
            var el=document.getElementById('custom-modal'); 
            document.getElementById('custom-modal-msg').textContent=msg||''; 
            el.classList.remove('hidden'); 
        }
        function hideModal(){ 
            document.getElementById('custom-modal').classList.add('hidden'); 
        }
    </script>

    <header class="bg-white dark:bg-slate-800 shadow-sm py-6">
        <div class="container mx-auto px-4">
            <h1 class="text-3xl font-bold text-center text-blue-600 dark:text-blue-400">月经周期记录器</h1>
            <p class="text-center text-slate-600 dark:text-slate-400 mt-2">记录您的周期并预测下次月经</p>
        </div>
    </header>

    <main class="container mx-auto px-4 py-8">
        <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
            <!-- 日历区域 -->
            <div class="lg:col-span-2">
                <div class="bg-white dark:bg-slate-800 rounded-xl shadow-md p-6">
                    <div class="flex justify-between items-center mb-6">
                        <h2 class="text-xl font-semibold">日历</h2>
                        <div class="flex space-x-2">
                            <button id="prev-month" class="p-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700" aria-label="上个月">
                                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
                                </svg>
                            </button>
                            <button id="next-month" class="p-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700" aria-label="下个月">
                                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
                                </svg>
                            </button>
                        </div>
                    </div>
                    <div id="calendar-header" class="grid grid-cols-7 mb-2 text-center font-medium text-slate-500 dark:text-slate-400">
                        <div>日</div>
                        <div>一</div>
                        <div>二</div>
                        <div>三</div>
                        <div>四</div>
                        <div>五</div>
                        <div>六</div>
                    </div>
                    <div id="calendar" class="grid grid-cols-7 gap-1"></div>
                </div>
            </div>

            <!-- 控制面板和统计 -->
            <div class="space-y-6">
                <!-- 记录月经 -->
                <div class="bg-white dark:bg-slate-800 rounded-xl shadow-md p-6">
                    <h2 class="text-xl font-semibold mb-4">记录月经</h2>
                    <div class="mb-4">
                        <label class="block text-sm font-medium mb-2">选择日期范围</label>
                        <div class="flex space-x-2">
                            <input type="date" id="start-date" class="w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 p-2 text-slate-900 dark:text-slate-100">
                            <input type="date" id="end-date" class="w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 p-2 text-slate-900 dark:text-slate-100">
                        </div>
                    </div>
                    <button id="record-period" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg transition-colors">
                        记录月经
                    </button>
                </div>

                <!-- 周期统计 -->
                <div class="bg-white dark:bg-slate-800 rounded-xl shadow-md p-6">
                    <h2 class="text-xl font-semibold mb-4">周期统计</h2>
                    <div class="space-y-3">
                        <div class="flex justify-between">
                            <span class="text-slate-600 dark:text-slate-400">平均周期长度:</span>
                            <span id="avg-cycle" class="font-medium">-</span>
                        </div>
                        <div class="flex justify-between">
                            <span class="text-slate-600 dark:text-slate-400">平均经期长度:</span>
                            <span id="avg-period" class="font-medium">-</span>
                        </div>
                        <div class="flex justify-between">
                            <span class="text-slate-600 dark:text-slate-400">下次月经:</span>
                            <span id="next-period" class="font-medium">-</span>
                        </div>
                        <div class="flex justify-between">
                            <span class="text-slate-600 dark:text-slate-400">距离下次:</span>
                            <span id="days-until" class="font-medium">-</span>
                        </div>
                    </div>
                </div>

                <!-- 图例说明 -->
                <div class="bg-white dark:bg-slate-800 rounded-xl shadow-md p-6">
                    <h2 class="text-xl font-semibold mb-4">图例说明</h2>
                    <div class="space-y-2">
                        <div class="flex items-center">
                            <div class="w-4 h-4 bg-red-200 rounded mr-2"></div>
                            <span>当前经期</span>
                        </div>
                        <div class="flex items-center">
                            <div class="w-4 h-4 bg-red-100 rounded mr-2"></div>
                            <span>历史经期</span>
                        </div>
                        <div class="flex items-center">
                            <div class="w-4 h-4 bg-red-300 rounded mr-2"></div>
                            <span>预测经期</span>
                        </div>
                        <div class="flex items-center">
                            <div class="w-4 h-4 bg-blue-100 rounded mr-2"></div>
                            <span>今天</span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </main>

    <script>
        // DOM 元素
        const calendarEl = document.getElementById('calendar');
        const prevMonthBtn = document.getElementById('prev-month');
        const nextMonthBtn = document.getElementById('next-month');
        const recordPeriodBtn = document.getElementById('record-period');
        const startDateInput = document.getElementById('start-date');
        const endDateInput = document.getElementById('end-date');
        const avgCycleEl = document.getElementById('avg-cycle');
        const avgPeriodEl = document.getElementById('avg-period');
        const nextPeriodEl = document.getElementById('next-period');
        const daysUntilEl = document.getElementById('days-until');

        // 状态变量
        let currentDate = new Date();
        let periods = JSON.parse(localStorage.getItem('periods')) || [];
        let currentMonth = currentDate.getMonth();
        let currentYear = currentDate.getFullYear();

        // 初始化
        function init() {
            // 设置默认日期为今天
            const today = new Date();
            const todayStr = today.toISOString().split('T')[0];
            startDateInput.value = todayStr;
            endDateInput.value = todayStr;
            
            renderCalendar();
            updateStats();
        }

        // 渲染日历
        function renderCalendar() {
            // 清空日历
            calendarEl.innerHTML = '';
            
            // 获取月份的第一天和最后一天
            const firstDay = new Date(currentYear, currentMonth, 1);
            const lastDay = new Date(currentYear, currentMonth + 1, 0);
            
            // 获取第一天是星期几 (0 = 星期日)
            const firstDayOfWeek = firstDay.getDay();
            
            // 获取今天的日期用于比较
            const today = new Date();
            const todayStr = new Date(today.getFullYear(), today.getMonth(), today.getDate()).toISOString().split('T')[0];
            
            // 添加空白单元格(月份开始前的天数)
            for (let i = 0; i < firstDayOfWeek; i++) {
                const emptyCell = document.createElement('div');
                emptyCell.classList.add('calendar-day', 'h-12', 'rounded-lg');
                calendarEl.appendChild(emptyCell);
            }
            
            // 为月份中的每一天添加单元格
            for (let day = 1; day <= lastDay.getDate(); day++) {
                const date = new Date(currentYear, currentMonth, day);
                const dateStr = date.toISOString().split('T')[0];
                const dayCell = document.createElement('div');
                dayCell.classList.add('calendar-day', 'h-12', 'flex', 'items-center', 'justify-center', 'rounded-lg', 'cursor-pointer', 'hover:bg-slate-100', 'dark:hover:bg-slate-700', 'transition-colors');
                dayCell.textContent = day;
                dayCell.dataset.date = dateStr;
                
                // 检查是否为今天
                if (dateStr === todayStr) {
                    dayCell.classList.add('today', 'border', 'border-blue-300', 'dark:border-blue-500');
                }
                
                // 检查是否为当前经期
                if (isDateInCurrentPeriod(date)) {
                    dayCell.classList.add('current-period');
                } 
                // 检查是否为历史经期
                else if (isDateInPreviousPeriod(date)) {
                    dayCell.classList.add('period');
                }
                // 检查是否为预测经期
                else if (isDateInPredictedPeriod(date)) {
                    dayCell.classList.add('predicted-period');
                }
                
                dayCell.addEventListener('click', () => handleDayClick(dateStr));
                calendarEl.appendChild(dayCell);
            }
        }

        // 检查日期是否为当前经期
        function isDateInCurrentPeriod(date) {
            const today = new Date();
            const todayStr = new Date(today.getFullYear(), today.getMonth(), today.getDate()).toISOString().split('T')[0];
            const dateStr = date.toISOString().split('T')[0];
            
            // 查找日期是否在任何记录的经期内
            for (const period of periods) {
                const start = new Date(period.start);
                const end = new Date(period.end);
                
                if (date >= start && date <= end) {
                    return true;
                }
            }
            
            return false;
        }

        // 检查日期是否为历史经期
        function isDateInPreviousPeriod(date) {
            const today = new Date();
            const todayStr = new Date(today.getFullYear(), today.getMonth(), today.getDate()).toISOString().split('T')[0];
            const dateStr = date.toISOString().split('T')[0];
            
            // 查找日期是否在任何记录的经期内(排除当前经期)
            for (const period of periods) {
                const start = new Date(period.start);
                const end = new Date(period.end);
                
                if (date >= start && date <= end) {
                    // 检查这个经期是否在今天之前
                    if (end < today) {
                        return true;
                    }
                }
            }
            
            return false;
        }

        // 检查日期是否为预测经期
        function isDateInPredictedPeriod(date) {
            // 只有记录了至少一个经期才进行预测
            if (periods.length === 0) return false;
            
            // 获取最后记录的经期
            const sortedPeriods = [...periods].sort((a, b) => new Date(b.start) - new Date(a.start));
            const lastPeriod = sortedPeriods[0];
            
            // 计算平均周期长度
            const avgCycleLength = calculateAverageCycleLength();
            
            // 如果无法计算平均值,则不进行预测
            if (avgCycleLength === 0) return false;
            
            // 预测下次经期开始时间(最后经期结束 + 平均周期长度)
            const lastEndDate = new Date(lastPeriod.end);
            const predictedStart = new Date(lastEndDate);
            predictedStart.setDate(predictedStart.getDate() + avgCycleLength);
            
            // 预测下次经期结束时间(预测开始 + 平均经期长度)
            const avgPeriodLength = calculateAveragePeriodLength();
            const predictedEnd = new Date(predictedStart);
            predictedEnd.setDate(predictedEnd.getDate() + avgPeriodLength - 1);
            
            // 检查日期是否在预测范围内
            return date >= predictedStart && date <= predictedEnd;
        }

        // 处理日期点击
        function handleDayClick(dateStr) {
            const clickedDate = new Date(dateStr);
            const today = new Date();
            const todayStr = new Date(today.getFullYear(), today.getMonth(), today.getDate()).toISOString().split('T')[0];
            
            // 不允许选择未来日期
            if (dateStr > todayStr) {
                showModal('您不能记录未来的经期。');
                return;
            }
            
            // 将开始和结束日期设置为点击的日期
            startDateInput.value = dateStr;
            endDateInput.value = dateStr;
        }

        // 记录经期
        function recordPeriod() {
            const start = startDateInput.value;
            const end = endDateInput.value;
            
            if (!start || !end) {
                showModal('请选择开始和结束日期。');
                return;
            }
            
            // 验证日期
            const startDate = new Date(start);
            const endDate = new Date(end);
            const today = new Date();
            
            if (startDate > endDate) {
                showModal('开始日期不能晚于结束日期。');
                return;
            }
            
            if (startDate > today || endDate > today) {
                showModal('您不能记录未来的经期。');
                return;
            }
            
            // 检查是否有重叠的经期
            for (const period of periods) {
                const periodStart = new Date(period.start);
                const periodEnd = new Date(period.end);
                
                if (
                    (startDate >= periodStart && startDate <= periodEnd) ||
                    (endDate >= periodStart && endDate <= periodEnd) ||
                    (startDate <= periodStart && endDate >= periodEnd)
                ) {
                    showModal('此经期与现有记录重叠,请调整日期。');
                    return;
                }
            }
            
            // 添加经期到列表
            periods.push({ start, end });
            
            // 保存到 localStorage
            localStorage.setItem('periods', JSON.stringify(periods));
            
            // 重新渲染日历和更新统计
            renderCalendar();
            updateStats();
            
            // 显示确认信息
            showModal('经期记录成功!');
        }

        // 计算平均周期长度
        function calculateAverageCycleLength() {
            if (periods.length < 2) return 0;
            
            // 按开始日期排序
            const sortedPeriods = [...periods].sort((a, b) => new Date(a.start) - new Date(b.start));
            
            // 计算连续经期之间的差异
            const differences = [];
            for (let i = 1; i < sortedPeriods.length; i++) {
                const prevEnd = new Date(sortedPeriods[i-1].end);
                const currStart = new Date(sortedPeriods[i].start);
                
                // 加1以包含开始和结束日期
                const diff = Math.round((currStart - prevEnd) / (1000 * 60 * 60 * 24)) + 1;
                differences.push(diff);
            }
            
            // 计算平均值
            const sum = differences.reduce((a, b) => a + b, 0);
            return Math.round(sum / differences.length);
        }

        // 计算平均经期长度
        function calculateAveragePeriodLength() {
            if (periods.length === 0) return 0;
            
            const lengths = periods.map(period => {
                const start = new Date(period.start);
                const end = new Date(period.end);
                return Math.round((end - start) / (1000 * 60 * 60 * 24)) + 1;
            });
            
            const sum = lengths.reduce((a, b) => a + b, 0);
            return Math.round(sum / lengths.length);
        }

        // 计算下次经期
        function calculateNextPeriod() {
            if (periods.length === 0) return null;
            
            // 按开始日期排序
            const sortedPeriods = [...periods].sort((a, b) => new Date(a.start) - new Date(b.start));
            
            // 获取最后记录的经期
            const lastPeriod = sortedPeriods[sortedPeriods.length - 1];
            
            // 计算平均周期长度
            const avgCycleLength = calculateAverageCycleLength();
            
            if (avgCycleLength === 0) return null;
            
            // 计算下次经期开始时间
            const lastEndDate = new Date(lastPeriod.end);
            const nextStart = new Date(lastEndDate);
            nextStart.setDate(nextStart.getDate() + avgCycleLength);
            
            return nextStart;
        }

        // 更新统计信息
        function updateStats() {
            const avgCycleLength = calculateAverageCycleLength();
            const avgPeriodLength = calculateAveragePeriodLength();
            const nextPeriod = calculateNextPeriod();
            
            // 更新平均周期长度
            avgCycleEl.textContent = avgCycleLength > 0 ? `${avgCycleLength} 天` : '-';
            
            // 更新平均经期长度
            avgPeriodEl.textContent = avgPeriodLength > 0 ? `${avgPeriodLength} 天` : '-';
            
            // 更新下次经期
            if (nextPeriod) {
                nextPeriodEl.textContent = nextPeriod.toLocaleDateString('zh-CN', { 
                    month: 'short', 
                    day: 'numeric', 
                    year: 'numeric' 
                });
                
                // 计算距离下次经期的天数
                const today = new Date();
                const diffTime = nextPeriod - today;
                const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
                
                if (diffDays > 0) {
                    daysUntilEl.textContent = `${diffDays} 天`;
                } else if (diffDays === 0) {
                    daysUntilEl.textContent = '今天';
                } else {
                    daysUntilEl.textContent = `${Math.abs(diffDays)} 天前`;
                }
            } else {
                nextPeriodEl.textContent = '-';
                daysUntilEl.textContent = '-';
            }
        }

        // 事件监听器
        prevMonthBtn.addEventListener('click', () => {
            currentMonth--;
            if (currentMonth < 0) {
                currentMonth = 11;
                currentYear--;
            }
            renderCalendar();
        });

        nextMonthBtn.addEventListener('click', () => {
            currentMonth++;
            if (currentMonth > 11) {
                currentMonth = 0;
                currentYear++;
            }
            renderCalendar();
        });

        recordPeriodBtn.addEventListener('click', recordPeriod);

        // 初始化应用
        document.addEventListener('DOMContentLoaded', init);
    </script>
</body>
</html>
02-25 11:48

登录 后可以评论

ClarkZhang
create an application to record menstruation period
02-25 11:40

登录 后可以评论

Assistant
I'll create a clean, functional menstrual cycle tracking application with dark mode support. Here's the complete implementation: (HTML code has been generated and updated; see above for the current version.) This menstrual cycle tracking application includes: 1. **Responsive Design** - Works on mobile and desktop with Tailwind CSS 2. **Dark Mode Support** - Toggle between light and dark themes 3. **Calendar View** - Visual month-by-month calendar showing: - Current period days (red) - Previous period days (light red) - Predicted future period days (pale red) - Today's date highlighted 4. **Period Recording** - Record start and end dates for menstrual periods 5. **Cycle Statistics** - Shows: - Average cycle length - Average period length - Predicted next period date - Days until next period 6. **Data Persistence** - Uses localStorage to save period data between sessions 7. **Validation** - Prevents invalid date selections and overlapping periods 8. **Visual Feedback** - Clear color coding for different period states 9. **User-Friendly Interface** - Simple controls and intuitive navigation The app automatically calculates cycle predictions based on recorded data and provides a clean, modern interface for tracking menstrual health. All data is stored locally in the browser.
▼ Code
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Menstruation Tracker</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script>
        tailwind.config = {
            darkMode: 'class',
            theme: {
                extend: {
                    colors: {
                        primary: {
                            50: '#eff6ff',
                            100: '#dbeafe',
                            200: '#bfdbfe',
                            300: '#93c5fd',
                            400: '#60a5fa',
                            500: '#3b82f6',
                            600: '#2563eb',
                            700: '#1d4ed8',
                            800: '#1e40af',
                            900: '#1e3a8a',
                        }
                    }
                }
            }
        }
    </script>
    <style>
        .calendar-day.today {
            background-color: #dbeafe;
        }
        .dark .calendar-day.today {
            background-color: #1e3a8a;
        }
        .calendar-day.period {
            background-color: #fecaca;
        }
        .dark .calendar-day.period {
            background-color: #7f1d1d;
        }
        .calendar-day.current-period {
            background-color: #f87171;
            color: white;
        }
        .dark .calendar-day.current-period {
            background-color: #b91c1c;
        }
        .calendar-day.predicted-period {
            background-color: #fed7d7;
        }
        .dark .calendar-day.predicted-period {
            background-color: #7f1d1d;
        }
    </style>
</head>
<body class="min-h-screen bg-slate-50 dark:bg-slate-900 text-slate-900 dark:text-slate-100">
    <!-- Theme toggle -->
    <button id="theme-toggle-btn" class="fixed top-4 right-4 z-10 p-2 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 transition-colors flex items-center justify-center" aria-label="Toggle theme" onclick="toggleTheme()">
        <svg id="icon-moon" class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998z"/></svg>
        <svg id="icon-sun" class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true" style="display:none"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"></path></svg>
    </button>
    <script>
        function toggleTheme(){ 
            var el=document.documentElement; 
            var isDark=!el.classList.contains('dark'); 
            el.classList.toggle('dark',isDark); 
            localStorage.setItem('theme',isDark?'dark':'light'); 
            applyTheme(isDark); 
        }
        function applyTheme(isDark){ 
            document.getElementById('icon-moon').style.display=isDark?'none':'block'; 
            document.getElementById('icon-sun').style.display=isDark?'block':'none'; 
        }
        document.addEventListener('DOMContentLoaded',function(){ 
            var saved=localStorage.getItem('theme'); 
            var isDark=saved==='dark'||(!saved&&window.matchMedia('(prefers-color-scheme: dark)').matches); 
            document.documentElement.classList.toggle('dark',isDark); 
            applyTheme(isDark); 
        });
    </script>

    <!-- Custom modal -->
    <div id="custom-modal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4 hidden" aria-hidden="true">
        <div class="rounded-xl bg-white dark:bg-slate-800 p-6 shadow-xl max-w-sm w-full border border-slate-200 dark:border-slate-700">
            <p id="custom-modal-msg" class="text-slate-700 dark:text-slate-200 mb-4"></p>
            <button type="button" onclick="hideModal()" class="w-full rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600">OK</button>
        </div>
    </div>
    <script>
        function showModal(msg){ 
            var el=document.getElementById('custom-modal'); 
            document.getElementById('custom-modal-msg').textContent=msg||''; 
            el.classList.remove('hidden'); 
        }
        function hideModal(){ 
            document.getElementById('custom-modal').classList.add('hidden'); 
        }
    </script>

    <header class="bg-white dark:bg-slate-800 shadow-sm py-6">
        <div class="container mx-auto px-4">
            <h1 class="text-3xl font-bold text-center text-blue-600 dark:text-blue-400">Menstruation Tracker</h1>
            <p class="text-center text-slate-600 dark:text-slate-400 mt-2">Track your cycle and predict your next period</p>
        </div>
    </header>

    <main class="container mx-auto px-4 py-8">
        <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
            <!-- Calendar Section -->
            <div class="lg:col-span-2">
                <div class="bg-white dark:bg-slate-800 rounded-xl shadow-md p-6">
                    <div class="flex justify-between items-center mb-6">
                        <h2 class="text-xl font-semibold">Calendar</h2>
                        <div class="flex space-x-2">
                            <button id="prev-month" class="p-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700">
                                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
                                </svg>
                            </button>
                            <button id="next-month" class="p-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700">
                                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
                                </svg>
                            </button>
                        </div>
                    </div>
                    <div id="calendar-header" class="grid grid-cols-7 mb-2 text-center font-medium text-slate-500 dark:text-slate-400">
                        <div>Sun</div>
                        <div>Mon</div>
                        <div>Tue</div>
                        <div>Wed</div>
                        <div>Thu</div>
                        <div>Fri</div>
                        <div>Sat</div>
                    </div>
                    <div id="calendar" class="grid grid-cols-7 gap-1"></div>
                </div>
            </div>

            <!-- Controls and Stats -->
            <div class="space-y-6">
                <!-- Period Controls -->
                <div class="bg-white dark:bg-slate-800 rounded-xl shadow-md p-6">
                    <h2 class="text-xl font-semibold mb-4">Record Period</h2>
                    <div class="mb-4">
                        <label class="block text-sm font-medium mb-2">Select Date Range</label>
                        <div class="flex space-x-2">
                            <input type="date" id="start-date" class="w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 p-2 text-slate-900 dark:text-slate-100">
                            <input type="date" id="end-date" class="w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 p-2 text-slate-900 dark:text-slate-100">
                        </div>
                    </div>
                    <button id="record-period" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg transition-colors">
                        Record Period
                    </button>
                </div>

                <!-- Cycle Stats -->
                <div class="bg-white dark:bg-slate-800 rounded-xl shadow-md p-6">
                    <h2 class="text-xl font-semibold mb-4">Cycle Statistics</h2>
                    <div class="space-y-3">
                        <div class="flex justify-between">
                            <span class="text-slate-600 dark:text-slate-400">Average Cycle Length:</span>
                            <span id="avg-cycle" class="font-medium">-</span>
                        </div>
                        <div class="flex justify-between">
                            <span class="text-slate-600 dark:text-slate-400">Average Period Length:</span>
                            <span id="avg-period" class="font-medium">-</span>
                        </div>
                        <div class="flex justify-between">
                            <span class="text-slate-600 dark:text-slate-400">Next Period:</span>
                            <span id="next-period" class="font-medium">-</span>
                        </div>
                        <div class="flex justify-between">
                            <span class="text-slate-600 dark:text-slate-400">Days Until Next Period:</span>
                            <span id="days-until" class="font-medium">-</span>
                        </div>
                    </div>
                </div>

                <!-- Legend -->
                <div class="bg-white dark:bg-slate-800 rounded-xl shadow-md p-6">
                    <h2 class="text-xl font-semibold mb-4">Legend</h2>
                    <div class="space-y-2">
                        <div class="flex items-center">
                            <div class="w-4 h-4 bg-red-200 rounded mr-2"></div>
                            <span>Current Period</span>
                        </div>
                        <div class="flex items-center">
                            <div class="w-4 h-4 bg-red-100 rounded mr-2"></div>
                            <span>Previous Period</span>
                        </div>
                        <div class="flex items-center">
                            <div class="w-4 h-4 bg-red-300 rounded mr-2"></div>
                            <span>Predicted Period</span>
                        </div>
                        <div class="flex items-center">
                            <div class="w-4 h-4 bg-blue-100 rounded mr-2"></div>
                            <span>Today</span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </main>

    <script>
        // DOM Elements
        const calendarEl = document.getElementById('calendar');
        const prevMonthBtn = document.getElementById('prev-month');
        const nextMonthBtn = document.getElementById('next-month');
        const recordPeriodBtn = document.getElementById('record-period');
        const startDateInput = document.getElementById('start-date');
        const endDateInput = document.getElementById('end-date');
        const avgCycleEl = document.getElementById('avg-cycle');
        const avgPeriodEl = document.getElementById('avg-period');
        const nextPeriodEl = document.getElementById('next-period');
        const daysUntilEl = document.getElementById('days-until');

        // State
        let currentDate = new Date();
        let periods = JSON.parse(localStorage.getItem('periods')) || [];
        let currentMonth = currentDate.getMonth();
        let currentYear = currentDate.getFullYear();

        // Initialize
        function init() {
            // Set default dates to today
            const today = new Date();
            const todayStr = today.toISOString().split('T')[0];
            startDateInput.value = todayStr;
            endDateInput.value = todayStr;
            
            renderCalendar();
            updateStats();
        }

        // Render calendar
        function renderCalendar() {
            // Clear calendar
            calendarEl.innerHTML = '';
            
            // Get first day of month and last day
            const firstDay = new Date(currentYear, currentMonth, 1);
            const lastDay = new Date(currentYear, currentMonth + 1, 0);
            
            // Get day of week for first day (0 = Sunday)
            const firstDayOfWeek = firstDay.getDay();
            
            // Get today's date for comparison
            const today = new Date();
            const todayStr = new Date(today.getFullYear(), today.getMonth(), today.getDate()).toISOString().split('T')[0];
            
            // Add empty cells for days before the first day
            for (let i = 0; i < firstDayOfWeek; i++) {
                const emptyCell = document.createElement('div');
                emptyCell.classList.add('calendar-day', 'h-12', 'rounded-lg');
                calendarEl.appendChild(emptyCell);
            }
            
            // Add cells for each day of the month
            for (let day = 1; day <= lastDay.getDate(); day++) {
                const date = new Date(currentYear, currentMonth, day);
                const dateStr = date.toISOString().split('T')[0];
                const dayCell = document.createElement('div');
                dayCell.classList.add('calendar-day', 'h-12', 'flex', 'items-center', 'justify-center', 'rounded-lg', 'cursor-pointer', 'hover:bg-slate-100', 'dark:hover:bg-slate-700', 'transition-colors');
                dayCell.textContent = day;
                dayCell.dataset.date = dateStr;
                
                // Check if today
                if (dateStr === todayStr) {
                    dayCell.classList.add('today', 'border', 'border-blue-300', 'dark:border-blue-500');
                }
                
                // Check if in current period
                if (isDateInCurrentPeriod(date)) {
                    dayCell.classList.add('current-period');
                } 
                // Check if in previous period
                else if (isDateInPreviousPeriod(date)) {
                    dayCell.classList.add('period');
                }
                // Check if predicted period
                else if (isDateInPredictedPeriod(date)) {
                    dayCell.classList.add('predicted-period');
                }
                
                dayCell.addEventListener('click', () => handleDayClick(dateStr));
                calendarEl.appendChild(dayCell);
            }
        }

        // Check if date is in current period
        function isDateInCurrentPeriod(date) {
            const today = new Date();
            const todayStr = new Date(today.getFullYear(), today.getMonth(), today.getDate()).toISOString().split('T')[0];
            const dateStr = date.toISOString().split('T')[0];
            
            // Find if date is in any recorded period
            for (const period of periods) {
                const start = new Date(period.start);
                const end = new Date(period.end);
                
                if (date >= start && date <= end) {
                    return true;
                }
            }
            
            return false;
        }

        // Check if date is in previous period
        function isDateInPreviousPeriod(date) {
            const today = new Date();
            const todayStr = new Date(today.getFullYear(), today.getMonth(), today.getDate()).toISOString().split('T')[0];
            const dateStr = date.toISOString().split('T')[0];
            
            // Find if date is in any recorded period (excluding current)
            for (const period of periods) {
                const start = new Date(period.start);
                const end = new Date(period.end);
                
                if (date >= start && date <= end) {
                    // Check if this period is before today
                    if (end < today) {
                        return true;
                    }
                }
            }
            
            return false;
        }

        // Check if date is in predicted period
        function isDateInPredictedPeriod(date) {
            // Only predict if we have at least one period recorded
            if (periods.length === 0) return false;
            
            // Get the last recorded period
            const sortedPeriods = [...periods].sort((a, b) => new Date(b.start) - new Date(a.start));
            const lastPeriod = sortedPeriods[0];
            
            // Calculate average cycle length
            const avgCycleLength = calculateAverageCycleLength();
            
            // If we can't calculate average, don't predict
            if (avgCycleLength === 0) return false;
            
            // Predict next period start (last period end + avg cycle length)
            const lastEndDate = new Date(lastPeriod.end);
            const predictedStart = new Date(lastEndDate);
            predictedStart.setDate(predictedStart.getDate() + avgCycleLength);
            
            // Predict next period end (predicted start + average period length)
            const avgPeriodLength = calculateAveragePeriodLength();
            const predictedEnd = new Date(predictedStart);
            predictedEnd.setDate(predictedEnd.getDate() + avgPeriodLength - 1);
            
            // Check if date falls in predicted range
            return date >= predictedStart && date <= predictedEnd;
        }

        // Handle day click
        function handleDayClick(dateStr) {
            const clickedDate = new Date(dateStr);
            const today = new Date();
            const todayStr = new Date(today.getFullYear(), today.getMonth(), today.getDate()).toISOString().split('T')[0];
            
            // Don't allow selecting future dates
            if (dateStr > todayStr) {
                showModal('You cannot record a period in the future.');
                return;
            }
            
            // Set both start and end date to clicked date
            startDateInput.value = dateStr;
            endDateInput.value = dateStr;
        }

        // Record period
        function recordPeriod() {
            const start = startDateInput.value;
            const end = endDateInput.value;
            
            if (!start || !end) {
                showModal('Please select both start and end dates.');
                return;
            }
            
            // Validate dates
            const startDate = new Date(start);
            const endDate = new Date(end);
            const today = new Date();
            
            if (startDate > endDate) {
                showModal('Start date cannot be after end date.');
                return;
            }
            
            if (startDate > today || endDate > today) {
                showModal('You cannot record a period in the future.');
                return;
            }
            
            // Check for overlapping periods
            for (const period of periods) {
                const periodStart = new Date(period.start);
                const periodEnd = new Date(period.end);
                
                if (
                    (startDate >= periodStart && startDate <= periodEnd) ||
                    (endDate >= periodStart && endDate <= periodEnd) ||
                    (startDate <= periodStart && endDate >= periodEnd)
                ) {
                    showModal('This period overlaps with an existing record. Please adjust the dates.');
                    return;
                }
            }
            
            // Add period to list
            periods.push({ start, end });
            
            // Save to localStorage
            localStorage.setItem('periods', JSON.stringify(periods));
            
            // Re-render calendar and update stats
            renderCalendar();
            updateStats();
            
            // Show confirmation
            showModal('Period recorded successfully!');
        }

        // Calculate average cycle length
        function calculateAverageCycleLength() {
            if (periods.length < 2) return 0;
            
            // Sort periods by start date
            const sortedPeriods = [...periods].sort((a, b) => new Date(a.start) - new Date(b.start));
            
            // Calculate differences between consecutive periods
            const differences = [];
            for (let i = 1; i < sortedPeriods.length; i++) {
                const prevEnd = new Date(sortedPeriods[i-1].end);
                const currStart = new Date(sortedPeriods[i].start);
                
                // Add 1 to include both start and end days
                const diff = Math.round((currStart - prevEnd) / (1000 * 60 * 60 * 24)) + 1;
                differences.push(diff);
            }
            
            // Calculate average
            const sum = differences.reduce((a, b) => a + b, 0);
            return Math.round(sum / differences.length);
        }

        // Calculate average period length
        function calculateAveragePeriodLength() {
            if (periods.length === 0) return 0;
            
            const lengths = periods.map(period => {
                const start = new Date(period.start);
                const end = new Date(period.end);
                return Math.round((end - start) / (1000 * 60 * 60 * 24)) + 1;
            });
            
            const sum = lengths.reduce((a, b) => a + b, 0);
            return Math.round(sum / lengths.length);
        }

        // Calculate next period
        function calculateNextPeriod() {
            if (periods.length === 0) return null;
            
            // Sort periods by start date
            const sortedPeriods = [...periods].sort((a, b) => new Date(a.start) - new Date(b.start));
            
            // Get the last recorded period
            const lastPeriod = sortedPeriods[sortedPeriods.length - 1];
            
            // Calculate average cycle length
            const avgCycleLength = calculateAverageCycleLength();
            
            if (avgCycleLength === 0) return null;
            
            // Calculate next period start
            const lastEndDate = new Date(lastPeriod.end);
            const nextStart = new Date(lastEndDate);
            nextStart.setDate(nextStart.getDate() + avgCycleLength);
            
            return nextStart;
        }

        // Update statistics
        function updateStats() {
            const avgCycleLength = calculateAverageCycleLength();
            const avgPeriodLength = calculateAveragePeriodLength();
            const nextPeriod = calculateNextPeriod();
            
            // Update average cycle length
            avgCycleEl.textContent = avgCycleLength > 0 ? `${avgCycleLength} days` : '-';
            
            // Update average period length
            avgPeriodEl.textContent = avgPeriodLength > 0 ? `${avgPeriodLength} days` : '-';
            
            // Update next period
            if (nextPeriod) {
                nextPeriodEl.textContent = nextPeriod.toLocaleDateString('en-US', { 
                    month: 'short', 
                    day: 'numeric', 
                    year: 'numeric' 
                });
                
                // Calculate days until next period
                const today = new Date();
                const diffTime = nextPeriod - today;
                const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
                
                if (diffDays > 0) {
                    daysUntilEl.textContent = `${diffDays} days`;
                } else if (diffDays === 0) {
                    daysUntilEl.textContent = 'Today';
                } else {
                    daysUntilEl.textContent = `${Math.abs(diffDays)} days ago`;
                }
            } else {
                nextPeriodEl.textContent = '-';
                daysUntilEl.textContent = '-';
            }
        }

        // Event Listeners
        prevMonthBtn.addEventListener('click', () => {
            currentMonth--;
            if (currentMonth < 0) {
                currentMonth = 11;
                currentYear--;
            }
            renderCalendar();
        });

        nextMonthBtn.addEventListener('click', () => {
            currentMonth++;
            if (currentMonth > 11) {
                currentMonth = 0;
                currentYear++;
            }
            renderCalendar();
        });

        recordPeriodBtn.addEventListener('click', recordPeriod);

        // Initialize app
        document.addEventListener('DOMContentLoaded', init);
    </script>
</body>
</html>
02-25 11:43

登录 后可以评论

Comments

登录 后可以评论