- Flomo 风格左右布局(侧边栏+主内容区) - 日历导航:月视图,三色状态圆点,点击日期快速切换 - 四层修为:立志/责善/改过/勤学 - 勤学 8 个固定预设项目,勾选后显示备注 - 编辑后 1.5 秒自动保存,切换日期静默保存 - Flask + SQLite,端口 5056
659 lines
24 KiB
JavaScript
659 lines
24 KiB
JavaScript
/* 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 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,'<').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<navs.length; i++) navs[i].classList.remove('active');
|
||
for (var j=0; j<panels.length; j++) panels[j].classList.remove('active');
|
||
|
||
var nav = document.querySelector('.nav-item[data-panel="' + name + '"]');
|
||
var panel = document.getElementById('panel-' + name);
|
||
if (nav) nav.classList.add('active');
|
||
if (panel) panel.classList.add('active');
|
||
|
||
if (name === 'weekly') loadWeekly();
|
||
if (name === 'history') loadHistory();
|
||
};
|
||
|
||
/* ================================================================
|
||
Calendar
|
||
================================================================ */
|
||
|
||
/** 格式化日期 */
|
||
function fmtDate(d) {
|
||
var y = d.getFullYear();
|
||
var m = ('0'+(d.getMonth()+1)).slice(-2);
|
||
var dd = ('0'+d.getDate()).slice(-2);
|
||
return y + '-' + m + '-' + dd;
|
||
}
|
||
|
||
/** 判断两个日期是否同一天 */
|
||
function isSameDay(a, b) {
|
||
return a.getFullYear() === b.getFullYear() &&
|
||
a.getMonth() === b.getMonth() &&
|
||
a.getDate() === b.getDate();
|
||
}
|
||
|
||
/** 切换日历月份 */
|
||
window.changeCalMonth = function(dir) {
|
||
calMonth += dir;
|
||
if (calMonth > 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 += '<div class="cal-cell other-month">' + (prevMonthLastDay - p) + '</div>';
|
||
}
|
||
|
||
// 当月
|
||
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');
|
||
var status = calendarStatus[ds];
|
||
if (status) classes.push(status);
|
||
|
||
html += '<div class="' + classes.join(' ') + '" data-date="' + ds + '">' + d + '</div>';
|
||
}
|
||
|
||
// 后月填充 — 始终 42 格(6行 × 7列),保持高度一致
|
||
var totalCells = startDayOfWeek + totalDays;
|
||
var remaining = 42 - totalCells;
|
||
for (var n = 1; n <= remaining; n++) {
|
||
html += '<div class="cal-cell other-month">' + n + '</div>';
|
||
}
|
||
|
||
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) {
|
||
switchPanel('daily');
|
||
document.getElementById('check-date').value = date;
|
||
loadCheckin();
|
||
}
|
||
|
||
/** 回到今天 */
|
||
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<mEls.length; i++) {
|
||
var v = mEls[i].value.trim();
|
||
if (v) morningItems.push(v);
|
||
}
|
||
|
||
var eveningItems = [];
|
||
var eRows = document.querySelectorAll('#evening-list .evening-row');
|
||
for (var j=0; j<eRows.length; j++) {
|
||
var cols = eRows[j].querySelectorAll('.col input');
|
||
var mistake = cols[0] ? cols[0].value.trim() : '';
|
||
var improve = cols[1] ? cols[1].value.trim() : '';
|
||
if (mistake || improve) eveningItems.push({ mistake: mistake, improvement: improve });
|
||
}
|
||
|
||
var studyItems = [];
|
||
var pItems = document.querySelectorAll('#study-list .preset-item');
|
||
for (var k=0; k<pItems.length; k++) {
|
||
var cb = pItems[k].querySelector('input[type="checkbox"]');
|
||
var noteEl = pItems[k].querySelector('.preset-note');
|
||
var nameEl = pItems[k].querySelector('.preset-name');
|
||
var name = nameEl ? nameEl.textContent : '';
|
||
if (cb && cb.checked) {
|
||
studyItems.push({ name: name, done: true, note: noteEl ? noteEl.value.trim() : '' });
|
||
} else {
|
||
studyItems.push({ name: name, done: false, note: '' });
|
||
}
|
||
}
|
||
|
||
return { morning: morningItems, evening: eveningItems, study: studyItems };
|
||
}
|
||
|
||
function fillForm(data) {
|
||
data = data || {};
|
||
var morning = data.morning || [];
|
||
var evening = data.evening || [];
|
||
var study = data.study || [];
|
||
|
||
// 早间立志
|
||
document.getElementById('morning-list').innerHTML = '';
|
||
if (morning.length === 0) morning = [''];
|
||
for (var m=0; m<morning.length; m++) addMorningRow(morning[m]);
|
||
|
||
// 责善改过
|
||
document.getElementById('evening-list').innerHTML = '';
|
||
if (evening.length === 0) evening = [{}];
|
||
for (var e=0; e<evening.length; e++) {
|
||
var item = typeof evening[e] === 'string' ? {mistake: evening[e], improvement: ''} : evening[e];
|
||
addEveningRow(item.mistake || '', item.improvement || '');
|
||
}
|
||
|
||
// 勤学 — 渲染预设项目并回填状态
|
||
var studyMap = {};
|
||
for (var si=0; si<study.length; si++) {
|
||
studyMap[study[si].name] = study[si];
|
||
}
|
||
document.getElementById('study-list').innerHTML = '';
|
||
for (var sp=0; sp<PRESET_STUDY_ITEMS.length; sp++) {
|
||
var sName = PRESET_STUDY_ITEMS[sp];
|
||
var saved = studyMap[sName] || { done: false, note: '' };
|
||
addPresetItem(sName, saved.done, saved.note || '');
|
||
}
|
||
}
|
||
|
||
/* ================================================================
|
||
Dynamic Rows
|
||
================================================================ */
|
||
|
||
function addMorningRow(val) {
|
||
var container = document.getElementById('morning-list');
|
||
var idx = container.children.length;
|
||
val = val || '';
|
||
var div = document.createElement('div');
|
||
div.className = 'item-row';
|
||
div.innerHTML = '<span class="idx">' + (idx+1) + '.</span>' +
|
||
'<input type="text" value="' + esc(val) + '" placeholder="今天最重要的一件事…">' +
|
||
'<button class="btn-del" onclick="this.parentElement.remove();renumberMorning()"><svg class="icon-sm"><use href="#icon-x"/></svg></button>';
|
||
container.appendChild(div);
|
||
renumberMorning();
|
||
}
|
||
|
||
function renumberMorning() {
|
||
var rows = document.querySelectorAll('#morning-list .item-row');
|
||
for (var i=0; i<rows.length; i++) {
|
||
var span = rows[i].querySelector('.idx');
|
||
if (span) span.textContent = (i+1) + '.';
|
||
}
|
||
}
|
||
|
||
function addEveningRow(mistake, improve) {
|
||
var container = document.getElementById('evening-list');
|
||
var idx = container.children.length;
|
||
mistake = mistake || '';
|
||
improve = improve || '';
|
||
var div = document.createElement('div');
|
||
div.className = 'evening-row';
|
||
div.innerHTML =
|
||
'<div class="evening-header">' +
|
||
'<span class="idx">' + (idx+1) + '.</span>' +
|
||
'<button class="btn-del" onclick="this.parentElement.parentElement.remove();renumberEvening()"><svg class="icon-sm"><use href="#icon-x"/></svg></button>' +
|
||
'</div>' +
|
||
'<div class="mistake-row">' +
|
||
'<div class="col"><label>错误</label><input type="text" value="' + esc(mistake) + '" placeholder="今天犯的错…"></div>' +
|
||
'<div class="col"><label>改进方案</label><input type="text" value="' + esc(improve) + '" placeholder="下次怎么做…"></div>' +
|
||
'</div>';
|
||
container.appendChild(div);
|
||
renumberEvening();
|
||
}
|
||
|
||
function renumberEvening() {
|
||
var rows = document.querySelectorAll('#evening-list .evening-row');
|
||
for (var i=0; i<rows.length; i++) {
|
||
var span = rows[i].querySelector('.idx');
|
||
if (span) span.textContent = (i+1) + '.';
|
||
}
|
||
}
|
||
|
||
/* ── 勤学预设项目 ── */
|
||
|
||
function addPresetItem(name, done, note) {
|
||
var container = document.getElementById('study-list');
|
||
var div = document.createElement('div');
|
||
div.className = 'preset-item' + (done ? ' active' : '');
|
||
var noteEsc = esc(note || '');
|
||
div.innerHTML =
|
||
'<label class="preset-check">' +
|
||
'<input type="checkbox"' + (done ? ' checked' : '') + ' onchange="togglePresetNote(this)">' +
|
||
'<span class="preset-name">' + esc(name) + '</span>' +
|
||
'</label>' +
|
||
'<textarea class="preset-note" placeholder="一句话备注…">' + noteEsc + '</textarea>';
|
||
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<PRESET_STUDY_ITEMS.length; i++) {
|
||
addPresetItem(PRESET_STUDY_ITEMS[i], false, '');
|
||
}
|
||
}
|
||
|
||
window.addMorning = function() {
|
||
var count = document.querySelectorAll('#morning-list .item-row').length;
|
||
if (count >= 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<rows.length; i++) map[rows[i].date] = rows[i].data;
|
||
|
||
var totalDays = 0, totalScore = 0, dayCells = [];
|
||
|
||
for (var j=0; j<7; j++) {
|
||
var cur = new Date(monday);
|
||
cur.setDate(monday.getDate() + j);
|
||
var ds = fmtDate(cur);
|
||
var dd = map[ds];
|
||
|
||
if (dd) {
|
||
totalDays++;
|
||
var dayScore = 0;
|
||
var morning = dd.morning || [];
|
||
var hasMorning = morning.some(function(x){ return x && typeof x === 'string' && x.trim(); });
|
||
var evening = dd.evening || [];
|
||
var hasEvening = evening.some(function(x){
|
||
var mst = typeof x === 'string' ? x : (x.mistake || '');
|
||
return mst && typeof mst === 'string' && mst.trim();
|
||
});
|
||
var study = dd.study || [];
|
||
var hasStudy = study.some(function(x){ return x.done; });
|
||
if (hasMorning) dayScore++;
|
||
if (hasEvening) dayScore++;
|
||
if (hasStudy) dayScore++;
|
||
totalScore += dayScore;
|
||
var status = dayScore >= 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<dayCells.length; g++) {
|
||
var cell = dayCells[g];
|
||
var dateParts = cell.ds.split('-');
|
||
var shortDate = dateParts[1] + '/' + dateParts[2];
|
||
var badgeText = cell.status === 'pass' ? '完成' : (cell.status === 'fail' ? '未达标' : '未打卡');
|
||
gridHtml += '<div class="day-cell ' + cell.status + '">' +
|
||
'<div class="day-label">' + cell.weekday + '</div>' +
|
||
'<div class="day-date">' + shortDate + '</div>' +
|
||
'<div class="day-score">' + cell.dayScore + '<span style="font-size:11px;font-weight:400">/3</span></div>' +
|
||
'<div class="day-badge">' + badgeText + '</div>' +
|
||
'</div>';
|
||
}
|
||
document.getElementById('week-days-grid').innerHTML = gridHtml;
|
||
});
|
||
}
|
||
|
||
/* ================================================================
|
||
History
|
||
================================================================ */
|
||
function loadHistory() {
|
||
apiGetHistory(function(err, rows){
|
||
var el = document.getElementById('history-grid');
|
||
if (err) {
|
||
el.innerHTML = '<div class="empty-state"><div class="empty-icon"><svg viewBox="0 0 24 24"><use href="#icon-list-bullet"/></svg></div>加载失败</div>';
|
||
return;
|
||
}
|
||
if (!rows || rows.length === 0) {
|
||
el.innerHTML = '<div class="empty-state"><div class="empty-icon"><svg viewBox="0 0 24 24"><use href="#icon-list-bullet"/></svg></div>暂无历史记录</div>';
|
||
return;
|
||
}
|
||
|
||
var html = '';
|
||
for (var i=0; i<rows.length; i++) {
|
||
var r = rows[i];
|
||
var morning = (r.data.morning || []).filter(function(x){ return x && typeof x === 'string' && x.trim(); });
|
||
var evening = (r.data.evening || []).filter(function(x){
|
||
var mst = typeof x === 'string' ? x : (x.mistake || '');
|
||
return mst && typeof mst === 'string' && mst.trim();
|
||
});
|
||
var study = (r.data.study || []).filter(function(x){ return x.done; });
|
||
|
||
var tags = [];
|
||
if (morning.length) tags.push('<span><svg class="icon-xs" style="color:var(--primary)"><use href="#icon-sun"/></svg> 立志 ' + morning.length + '</span>');
|
||
if (evening.length) tags.push('<span><svg class="icon-xs" style="color:var(--danger)"><use href="#icon-magnifying-glass"/></svg> 改过 ' + evening.length + '</span>');
|
||
if (study.length) tags.push('<span><svg class="icon-xs" style="color:#059669"><use href="#icon-book-open"/></svg> 勤学 ' + study.length + '</span>');
|
||
|
||
html += '<div class="history-card">' +
|
||
'<div class="info">' +
|
||
'<div class="date">' + r.date + '</div>' +
|
||
'<div class="preview">' + (tags.join('') || '暂无内容') + '</div>' +
|
||
'</div>' +
|
||
'<button class="btn-del-history" onclick="deleteHistory(\'' + r.date + '\')"><svg class="icon-xs"><use href="#icon-trash"/></svg> 删除</button>' +
|
||
'</div>';
|
||
}
|
||
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;
|
||
|
||
initStudyPresets();
|
||
bindAutoSave();
|
||
lastSavedDate = todayStr;
|
||
loadCheckin();
|
||
loadStats();
|
||
}
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
} else {
|
||
init();
|
||
}
|
||
})();
|