/* ziwei-power 前端逻辑 — Flomo 风格左右布局 */ (function() { 'use strict'; /* ================================================================ State ================================================================ */ var calendarStatus = {}; // { "2026-06-01": "pass", ... } var calYear, calMonth; // 日历显示的月份 var activePanel = 'daily'; // 当前面板 var weekOffset = 0; // 每周评分偏移 var autoSaveTimer = null; // 自动保存防抖 var lastSavedData = null; // 上次保存快照 var lastSavedDate = null; // 上次保存日期 var selectedDate = null; // 日历中选中日期 /* 勤学预设项目 */ var PRESET_STUDY_ITEMS = [ '晚上 11 点 30 之前睡觉', '早上 6 点 30 之前起床', '起床后做 30 个俯卧撑', '销售客情能力提升', '管理能力提升', '产品规划有进一步的提升', 'AI 能力提升', '知识库能力提升' ]; /* ================================================================ Toast ================================================================ */ var toastTimer = null; function showToast(msg, type) { type = type || 'info'; var el = document.getElementById('toast'); el.textContent = msg; el.className = 'toast ' + type + ' show'; clearTimeout(toastTimer); toastTimer = setTimeout(function(){ el.classList.remove('show'); }, 2500); } /* ================================================================ API ================================================================ */ function apiGetCheckin(date, cb) { fetch('/api/checkin?date=' + encodeURIComponent(date)) .then(function(r){ return r.json(); }) .then(function(res){ if (res.ok) cb(null, res.data); else cb(res.error); }) .catch(function(e){ cb(e.message); }); } function apiSaveCheckin(date, data, cb) { fetch('/api/checkin', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({date: date, data: data}) }) .then(function(r){ return r.json(); }) .then(function(res){ if (res.ok) cb(null); else cb(res.error); }) .catch(function(e){ cb(e.message); }); } function apiDeleteCheckin(date, cb) { fetch('/api/checkin/' + encodeURIComponent(date), {method: 'DELETE'}) .then(function(r){ return r.json(); }) .then(function(res){ if (res.ok) cb(null); else cb(res.error); }) .catch(function(e){ cb(e.message); }); } function apiGetHistory(cb) { fetch('/api/history') .then(function(r){ return r.json(); }) .then(function(res){ if (res.ok) cb(null, res.data); else cb(res.error); }) .catch(function(e){ cb(e.message); }); } function apiGetStats(cb) { fetch('/api/stats') .then(function(r){ return r.json(); }) .then(function(res){ if (res.ok) cb(null, res.data); else cb(res.error); }) .catch(function(e){ cb(e.message); }); } /* ================================================================ Escaping ================================================================ */ function esc(s) { return String(s).replace(/&/g,'&').replace(/"/g,'"').replace(//g,'>'); } /* ================================================================ Panel Switching ================================================================ */ window.switchPanel = function(name) { activePanel = name; var navs = document.querySelectorAll('.sidebar-nav .nav-item'); var panels = document.querySelectorAll('.main-content .panel'); for (var i=0; i 12) { calMonth = 1; calYear++; } if (calMonth < 1) { calMonth = 12; calYear--; } renderCalendar(); }; /** 渲染日历 */ function renderCalendar() { var today = new Date(); var firstDay = new Date(calYear, calMonth - 1, 1); var lastDay = new Date(calYear, calMonth, 0); var startDayOfWeek = firstDay.getDay(); // 0=Sun var totalDays = lastDay.getDate(); var prevMonthLastDay = new Date(calYear, calMonth - 1, 0).getDate(); // 月份标签 document.getElementById('cal-month-label').textContent = calYear + '年 ' + calMonth + '月'; var grid = document.getElementById('cal-grid'); var html = ''; // 前月填充 for (var p = startDayOfWeek - 1; p >= 0; p--) { html += '
' + (prevMonthLastDay - p) + '
'; } // 当月 for (var d = 1; d <= totalDays; d++) { var cur = new Date(calYear, calMonth - 1, d); var ds = fmtDate(cur); var classes = ['cal-cell']; var isOtherMonth = false; if (isSameDay(cur, today)) classes.push('today'); if (ds === selectedDate) classes.push('selected'); var status = calendarStatus[ds]; if (status) classes.push(status); html += '
' + d + '
'; } // 后月填充 — 始终 42 格(6行 × 7列),保持高度一致 var totalCells = startDayOfWeek + totalDays; var remaining = 42 - totalCells; for (var n = 1; n <= remaining; n++) { html += '
' + n + '
'; } grid.innerHTML = html; // 点击事件 grid.querySelectorAll('.cal-cell:not(.other-month)').forEach(function(cell) { cell.addEventListener('click', function() { var date = this.dataset.date; if (date) navigateToDate(date); }); }); } /** 导航到指定日期 */ function navigateToDate(date) { selectedDate = date; switchPanel('daily'); document.getElementById('check-date').value = date; loadCheckin(); renderCalendar(); // 刷新日历高亮 } /** 回到今天 */ window.goToday = function() { var today = fmtDate(new Date()); calYear = new Date().getFullYear(); calMonth = new Date().getMonth() + 1; renderCalendar(); navigateToDate(today); }; /* ================================================================ Stats — 加载统计数据并更新侧边栏 ================================================================ */ function loadStats() { apiGetStats(function(err, data) { if (err) return; // 更新统计数字 document.getElementById('stat-days').textContent = data.total_days; document.getElementById('stat-morning').textContent = data.total_morning; document.getElementById('stat-study').textContent = data.total_study; // 更新日历状态 calendarStatus = data.calendar || {}; renderCalendar(); }); } /* ================================================================ Build / Fill Data ================================================================ */ function buildData() { var morningItems = []; var mEls = document.querySelectorAll('#morning-list .item-row input'); for (var i=0; i' + '' + ''; container.appendChild(div); renumberMorning(); } function renumberMorning() { var rows = document.querySelectorAll('#morning-list .item-row'); for (var i=0; i' + (idx+1) + '.' + '' + '' + '
' + '
' + '
' + '
'; container.appendChild(div); renumberEvening(); } function renumberEvening() { var rows = document.querySelectorAll('#evening-list .evening-row'); for (var i=0; i' + '' + esc(name) + '' + '' + ''; container.appendChild(div); } window.togglePresetNote = function(cb) { var item = cb.closest('.preset-item'); if (item) { if (cb.checked) item.classList.add('active'); else { item.classList.remove('active'); item.querySelector('.preset-note').value = ''; } } triggerAutoSave(); }; function initStudyPresets() { document.getElementById('study-list').innerHTML = ''; for (var i=0; i= 3) { showToast('最多 3 条立志', 'error'); return; } addMorningRow(''); }; window.addEvening = function() { var count = document.querySelectorAll('#evening-list .evening-row').length; if (count >= 5) { showToast('最多 5 条改过', 'error'); return; } addEveningRow('', ''); }; /* ================================================================ Load & Auto Save Checkin ================================================================ */ var lastSavedData = null; // 上次保存的数据快照,避免重复保存 window.loadCheckin = function() { var date = document.getElementById('check-date').value; if (!date) return; // 切换日期前先保存当前数据 var currentData = buildData(); var currentStr = JSON.stringify(currentData); if (lastSavedDate && lastSavedData !== null && currentStr !== lastSavedData) { // 有未保存的变更,先静默保存 var hasContent = currentData.morning.some(function(x){return x && x.trim();}) || currentData.evening.some(function(x){return (x.mistake||'').trim() || (x.improvement||'').trim();}) || currentData.study.some(function(x){return x.done;}); if (hasContent) { apiSaveCheckin(lastSavedDate, currentData, function(){}); // 静默保存 } } lastSavedDate = date; apiGetCheckin(date, function(err, row){ if (err) { initStudyPresets(); lastSavedData = '{}'; return; } fillForm(row ? row.data : null); lastSavedData = JSON.stringify(buildData()); }); }; /** 自动保存(带防抖) */ function triggerAutoSave() { clearTimeout(autoSaveTimer); autoSaveTimer = setTimeout(function() { var date = document.getElementById('check-date').value; if (!date) return; var data = buildData(); var dataStr = JSON.stringify(data); if (dataStr === lastSavedData) return; // 无变化,跳过 apiSaveCheckin(date, data, function(err){ if (err) { showToast('自动保存失败', 'error'); return; } lastSavedData = dataStr; showToast('已自动保存', 'info'); loadStats(); }); }, 1500); } /** 为每日打卡面板绑定自动保存事件 */ function bindAutoSave() { var panel = document.getElementById('panel-daily'); if (!panel) return; // 输入框 & 文本域:input 事件 panel.addEventListener('input', function(e) { if (e.target.matches('input[type="text"], textarea, input[type="date"]')) { triggerAutoSave(); } }); // 复选框:change 事件 panel.addEventListener('change', function(e) { if (e.target.matches('input[type="checkbox"]')) { triggerAutoSave(); } }); // 删除行按钮:点击后触发 panel.addEventListener('click', function(e) { var btn = e.target.closest('.btn-del'); if (btn) { setTimeout(triggerAutoSave, 100); // 等 DOM 更新后 } }); // 预设项目勾选切换 panel.addEventListener('change', function(e) { if (e.target.matches('.preset-check input[type="checkbox"]')) { triggerAutoSave(); } }); } /* ================================================================ Weekly Score ================================================================ */ var WEEKDAYS_CN = ['日','一','二','三','四','五','六']; function getMonday(d) { var day = d.getDay(); var diff = d.getDate() - day + (day === 0 ? -6 : 1); var m = new Date(d); m.setDate(diff); return m; } window.changeWeek = function(dir) { weekOffset += dir; loadWeekly(); }; function loadWeekly() { var today = new Date(); today.setDate(today.getDate() + weekOffset * 7); var monday = getMonday(today); var sunday = new Date(monday); sunday.setDate(monday.getDate() + 6); document.getElementById('week-label').textContent = fmtDate(monday) + ' ~ ' + fmtDate(sunday); apiGetHistory(function(err, rows){ if (err) { showToast('加载失败', 'error'); return; } var map = {}; for (var i=0; i= 2 ? 'pass' : 'fail'; dayCells.push({ ds: ds, dayScore: dayScore, status: status, weekday: '周' + WEEKDAYS_CN[cur.getDay()] }); } else { dayCells.push({ ds: ds, dayScore: 0, status: 'empty', weekday: '周' + WEEKDAYS_CN[cur.getDay()] }); } } var score = totalDays > 0 ? Math.round((totalScore / (totalDays * 3)) * 100) : 0; document.getElementById('weekly-score').textContent = score; var ring = document.querySelector('.score-ring'); if (ring) { var R = 50, C = 2 * Math.PI * R; ring.setAttribute('stroke-dasharray', C); ring.setAttribute('stroke-dashoffset', C - (C * score / 100)); } var txt = ''; if (score >= 90) txt = '卓越!磁场非常强大'; else if (score >= 70) txt = '良好,继续保持'; else if (score >= 50) txt = '一般,需要加强'; else txt = '较弱,急需调整'; document.getElementById('score-text').textContent = txt; var completedDays = dayCells.filter(function(c){ return c.status === 'pass'; }).length; var totalFilled = dayCells.filter(function(c){ return c.status !== 'empty'; }).length; document.getElementById('score-stats').textContent = '本周填卡 ' + totalFilled + '/7 天 · 达标 ' + completedDays + ' 天'; var gridHtml = ''; for (var g=0; g' + '
' + cell.weekday + '
' + '
' + shortDate + '
' + '
' + cell.dayScore + '/3
' + '
' + badgeText + '
' + ''; } document.getElementById('week-days-grid').innerHTML = gridHtml; }); } /* ================================================================ History ================================================================ */ function loadHistory() { apiGetHistory(function(err, rows){ var el = document.getElementById('history-grid'); if (err) { el.innerHTML = '
加载失败
'; return; } if (!rows || rows.length === 0) { el.innerHTML = '
暂无历史记录
'; return; } var html = ''; for (var i=0; i 立志 ' + morning.length + ''); if (evening.length) tags.push(' 改过 ' + evening.length + ''); if (study.length) tags.push(' 勤学 ' + study.length + ''); html += '
' + '
' + '
' + r.date + '
' + '
' + (tags.join('') || '暂无内容') + '
' + '
' + '' + '
'; } el.innerHTML = html; }); } window.deleteHistory = function(date) { if (!confirm('确定删除 ' + date + ' 的打卡记录吗?此操作不可恢复!')) return; apiDeleteCheckin(date, function(err){ if (err) { showToast('删除失败: ' + err, 'error'); return; } showToast('已删除', 'info'); loadHistory(); loadStats(); // 更新统计 }); }; /* ================================================================ Init ================================================================ */ function init() { var today = new Date(); calYear = today.getFullYear(); calMonth = today.getMonth() + 1; var cd = document.getElementById('check-date'); var todayStr = fmtDate(today); if (cd) cd.value = todayStr; selectedDate = todayStr; initStudyPresets(); bindAutoSave(); lastSavedDate = todayStr; loadCheckin(); loadStats(); renderCalendar(); // 立即渲染日历,不等异步统计 } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();