Files
ziwei-power/static/app.js

412 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ziwei-power 前端逻辑 — 通过 API 与 Flask 后端交互 */
(function() {
'use strict';
/* ── 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); });
}
/* ── Tab 切换 ───────────────────────────── */
window.switchTab = function(name, ev) {
var tabs = document.querySelectorAll('.tab');
var contents = document.querySelectorAll('.tab-content');
for (var i=0; i<tabs.length; i++) tabs[i].classList.remove('active');
for (var j=0; j<contents.length; j++) contents[j].classList.remove('active');
if (ev && ev.target) ev.target.classList.add('active');
var ct = document.getElementById(name + '-tab');
if (ct) ct.classList.add('active');
if (name === 'history') loadHistory();
if (name === 'weekly') loadWeekly();
};
/* ── 打卡数据构建 ───────────────────────── */
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 eGroups = document.querySelectorAll('#evening-list .mistake-group');
for (var j=0; j<eGroups.length; j++) {
var inputs = eGroups[j].querySelectorAll('input, textarea');
var mistake = inputs[0] ? inputs[0].value.trim() : '';
var improve = inputs[1] ? inputs[1].value.trim() : '';
if (mistake || improve) {
eveningItems.push({ mistake: mistake, improvement: improve });
}
}
// 勤学
var studyItems = [];
var sRows = document.querySelectorAll('#study-list .study-row');
for (var k=0; k<sRows.length; k++) {
var cb = sRows[k].querySelector('input[type="checkbox"]');
var txt = sRows[k].querySelector('input[type="text"]');
var name = txt ? txt.value.trim() : '';
if (name) {
studyItems.push({ name: name, done: cb ? cb.checked : false });
}
}
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 || '');
}
// 勤学
document.getElementById('study-list').innerHTML = '';
if (study.length === 0) study = [{name: '', done: false}];
for (var s=0; s<study.length; s++) {
addStudyRow(study[s].name || '', study[s].done || false);
}
}
/* ── 动态添加行 ──────────────────────────── */
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()">×</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 = 'item-row';
div.innerHTML = '<span class="idx">' + (idx+1) + '.</span>' +
'<div class="mistake-group">' +
'<span class="label-hint">错误</span>' +
'<input type="text" value="' + esc(mistake) + '" placeholder="今天犯的错…">' +
'<span class="label-hint">改进方案</span>' +
'<textarea placeholder="下次怎么做…">' + esc(improve) + '</textarea>' +
'</div>' +
'<button class="btn-del" onclick="this.parentElement.remove();renumberEvening()">×</button>';
container.appendChild(div);
renumberEvening();
}
function renumberEvening() {
var rows = document.querySelectorAll('#evening-list .item-row');
for (var i=0; i<rows.length; i++) {
var span = rows[i].querySelector('.idx');
if (span) span.textContent = (i+1) + '.';
}
}
function addStudyRow(name, done) {
var container = document.getElementById('study-list');
name = name || '';
done = done || false;
var div = document.createElement('div');
div.className = 'study-row';
div.innerHTML = '<input type="checkbox"' + (done ? ' checked' : '') + '>' +
'<input type="text" value="' + esc(name) + '" placeholder="学习项目名称…">' +
'<button class="btn-del" onclick="this.parentElement.remove()">×</button>';
container.appendChild(div);
}
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 .item-row').length;
if (count >= 5) { showToast('最多 5 条改过', 'error'); return; }
addEveningRow('', '');
};
window.addStudy = function() {
addStudyRow('', false);
};
/* ── 加载 & 保存 ────────────────────────── */
window.loadCheckin = function() {
var date = document.getElementById('check-date').value;
if (!date) return;
apiGetCheckin(date, function(err, row){
if (err) { fillForm(null); return; }
fillForm(row ? row.data : null);
});
};
window.saveCheckin = function() {
var date = document.getElementById('check-date').value;
if (!date) { showToast('请先选择日期!', 'error'); return; }
var data = buildData();
apiSaveCheckin(date, data, function(err){
if (err) { showToast('保存失败: ' + err, 'error'); return; }
showToast('✅ 打卡保存成功!', 'success');
});
};
/* ── 每周评分 ───────────────────────────── */
var weekOffset = 0;
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;
}
function fmtDate(d) {
var y = d.getFullYear();
var m = ('0'+(d.getMonth()+1)).slice(-2);
var day = ('0'+d.getDate()).slice(-2);
return y + '-' + m + '-' + day;
}
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;
var totalScore = 0;
var details = [];
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 = false;
for (var m=0; m<morning.length; m++) {
if (morning[m] && morning[m].trim) { if (morning[m].trim()) hasMorning = true; }
}
if (hasMorning) dayScore++;
// 改过有内容
var evening = dd.evening || [];
var hasEvening = false;
for (var e=0; e<evening.length; e++) {
var ev = evening[e];
var mst = typeof ev === 'string' ? ev : (ev.mistake || '');
if (mst.trim) { if (mst.trim()) hasEvening = true; }
}
if (hasEvening) dayScore++;
// 勤学有勾选
var study = dd.study || [];
var hasStudy = false;
for (var s=0; s<study.length; s++) {
if (study[s].done) hasStudy = true;
}
if (hasStudy) dayScore++;
totalScore += dayScore;
var icon = dayScore >= 2 ? '✅' : '⚠️';
var color = dayScore >= 2 ? '#10B981' : '#F59E0B';
details.push('<div class="detail-row" style="color:' + color + '">' +
ds + ' — 得分:' + dayScore + '/3 ' + icon + '</div>');
}
}
var score = totalDays > 0 ? Math.round((totalScore / (totalDays * 3)) * 100) : 0;
document.getElementById('weekly-score').textContent = score;
var txt = '';
if (score >= 90) txt = '🌟 卓越!磁场非常强大';
else if (score >= 70) txt = '💪 良好,继续保持';
else if (score >= 50) txt = '⚠️ 一般,需要加强';
else txt = '❌ 较弱,急需调整';
document.getElementById('score-text').textContent = txt;
document.getElementById('weekly-details').innerHTML = details.join('');
});
}
/* ── 历史记录 ───────────────────────────── */
function loadHistory() {
apiGetHistory(function(err, rows){
var el = document.getElementById('history-list');
if (err) { el.innerHTML = '<p style="text-align:center;color:#999;">加载失败</p>'; return; }
if (!rows || rows.length === 0) {
el.innerHTML = '<p style="text-align:center;color:#999;padding:40px 0;">暂无历史记录</p>';
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 && x.trim && x.trim();
});
var evening = (r.data.evening || []).filter(function(x){
var mst = typeof x === 'string' ? x : (x.mistake || '');
return mst && mst.trim && mst.trim();
});
var preview = [];
if (morning.length) preview.push('📝 立志 ' + morning.length + ' 条');
if (evening.length) preview.push('⚠️ 改过 ' + evening.length + ' 条');
html += '<div class="history-row">' +
'<div class="info">' +
'<div class="date">' + r.date + '</div>' +
'<div class="preview">' + (preview.join(' · ') || '暂无内容') + '</div>' +
'</div>' +
'<button class="btn-del-history" onclick="deleteHistory(\'' + r.date + '\')">🗑 删除</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();
});
};
/* ── HTML 转义 ──────────────────────────── */
function esc(s) {
return String(s).replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
/* ── 初始化 ────────────────────────────── */
function init() {
var today = new Date().toISOString().split('T')[0];
var cd = document.getElementById('check-date');
if (cd) { cd.value = today; }
fillForm(null);
loadCheckin();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();