v1.0.0 — 评分规则大改 & UI优化
- 责善改过:纵向排列、输入框样式对齐立志、扁平化去内层卡片嵌套 - 自动保存:blur 触发替代 input 防抖,静默保存 - 日评分:3项全有 → 60分 + 多出条数×5;缺失 → 条数×5 - 周评分:日峰值累加制,取消百分比上限 - 修复 preset-note 溢出、清理冗余代码
This commit is contained in:
27
app.py
27
app.py
@@ -84,26 +84,23 @@ def compute_stats():
|
|||||||
evening = data.get('evening', [])
|
evening = data.get('evening', [])
|
||||||
study = data.get('study', [])
|
study = data.get('study', [])
|
||||||
|
|
||||||
total_morning += sum(1 for x in morning if isinstance(x, str) and x.strip())
|
morning_count = sum(1 for x in morning if isinstance(x, str) and x.strip())
|
||||||
total_evening += sum(1 for x in evening if (
|
evening_count = sum(1 for x in evening if (
|
||||||
isinstance(x, str) and x.strip() or
|
isinstance(x, str) and x.strip() or
|
||||||
isinstance(x, dict) and (x.get('mistake', '') or '').strip()
|
isinstance(x, dict) and (x.get('mistake', '') or '').strip()
|
||||||
))
|
))
|
||||||
total_study += sum(1 for x in study if x.get('done'))
|
study_count = sum(1 for x in study if x.get('done'))
|
||||||
|
|
||||||
day_score = 0
|
total_morning += morning_count
|
||||||
has_morning = any(isinstance(x, str) and x.strip() for x in morning)
|
total_evening += evening_count
|
||||||
has_evening = any(
|
total_study += study_count
|
||||||
(isinstance(x, str) and x.strip()) or
|
|
||||||
(isinstance(x, dict) and (x.get('mistake', '') or '').strip())
|
|
||||||
for x in evening
|
|
||||||
)
|
|
||||||
has_study = any(x.get('done') for x in study)
|
|
||||||
if has_morning: day_score += 1
|
|
||||||
if has_evening: day_score += 1
|
|
||||||
if has_study: day_score += 1
|
|
||||||
|
|
||||||
calendar[d] = 'pass' if day_score >= 2 else 'fail'
|
# 新评分:3 项都有 → 达标 60 分 + 额外加分
|
||||||
|
all_three = morning_count > 0 and evening_count > 0 and study_count > 0
|
||||||
|
if all_three:
|
||||||
|
extra = (morning_count - 1) + (evening_count - 1) + (study_count - 1)
|
||||||
|
# day_score = 60 + extra * 5 (不在此处使用,仅用于日历状态)
|
||||||
|
calendar[d] = 'pass' if all_three else 'fail'
|
||||||
|
|
||||||
return dict(
|
return dict(
|
||||||
total_days=total_days, total_morning=total_morning,
|
total_days=total_days, total_morning=total_morning,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
var calYear, calMonth; // 日历显示的月份
|
var calYear, calMonth; // 日历显示的月份
|
||||||
var activePanel = 'daily'; // 当前面板
|
var activePanel = 'daily'; // 当前面板
|
||||||
var weekOffset = 0; // 每周评分偏移
|
var weekOffset = 0; // 每周评分偏移
|
||||||
var autoSaveTimer = null; // 自动保存防抖
|
|
||||||
var lastSavedData = null; // 上次保存快照
|
var lastSavedData = null; // 上次保存快照
|
||||||
var lastSavedDate = null; // 上次保存日期
|
var lastSavedDate = null; // 上次保存日期
|
||||||
var selectedDate = null; // 日历中选中日期
|
var selectedDate = null; // 日历中选中日期
|
||||||
@@ -399,7 +398,6 @@
|
|||||||
/* ================================================================
|
/* ================================================================
|
||||||
Load & Auto Save Checkin
|
Load & Auto Save Checkin
|
||||||
================================================================ */
|
================================================================ */
|
||||||
var lastSavedData = null; // 上次保存的数据快照,避免重复保存
|
|
||||||
|
|
||||||
window.loadCheckin = function() {
|
window.loadCheckin = function() {
|
||||||
var date = document.getElementById('check-date').value;
|
var date = document.getElementById('check-date').value;
|
||||||
@@ -426,22 +424,18 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 自动保存(带防抖) */
|
/** 自动保存 — 失去焦点/勾选/删除时触发 */
|
||||||
function triggerAutoSave() {
|
function triggerAutoSave() {
|
||||||
clearTimeout(autoSaveTimer);
|
var date = document.getElementById('check-date').value;
|
||||||
autoSaveTimer = setTimeout(function() {
|
if (!date) return;
|
||||||
var date = document.getElementById('check-date').value;
|
var data = buildData();
|
||||||
if (!date) return;
|
var dataStr = JSON.stringify(data);
|
||||||
var data = buildData();
|
if (dataStr === lastSavedData) return;
|
||||||
var dataStr = JSON.stringify(data);
|
apiSaveCheckin(date, data, function(err){
|
||||||
if (dataStr === lastSavedData) return; // 无变化,跳过
|
if (err) { showToast('保存失败', 'error'); return; }
|
||||||
apiSaveCheckin(date, data, function(err){
|
lastSavedData = dataStr;
|
||||||
if (err) { showToast('自动保存失败', 'error'); return; }
|
loadStats();
|
||||||
lastSavedData = dataStr;
|
});
|
||||||
showToast('已自动保存', 'info');
|
|
||||||
loadStats();
|
|
||||||
});
|
|
||||||
}, 1500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 为每日打卡面板绑定自动保存事件 */
|
/** 为每日打卡面板绑定自动保存事件 */
|
||||||
@@ -449,12 +443,12 @@
|
|||||||
var panel = document.getElementById('panel-daily');
|
var panel = document.getElementById('panel-daily');
|
||||||
if (!panel) return;
|
if (!panel) return;
|
||||||
|
|
||||||
// 输入框 & 文本域:input 事件
|
// 输入框 & 文本域:失去焦点时保存
|
||||||
panel.addEventListener('input', function(e) {
|
panel.addEventListener('blur', function(e) {
|
||||||
if (e.target.matches('input[type="text"], textarea, input[type="date"]')) {
|
if (e.target.matches('input[type="text"], textarea, input[type="date"]')) {
|
||||||
triggerAutoSave();
|
triggerAutoSave();
|
||||||
}
|
}
|
||||||
});
|
}, true);
|
||||||
|
|
||||||
// 复选框:change 事件
|
// 复选框:change 事件
|
||||||
panel.addEventListener('change', function(e) {
|
panel.addEventListener('change', function(e) {
|
||||||
@@ -470,13 +464,6 @@
|
|||||||
setTimeout(triggerAutoSave, 100); // 等 DOM 更新后
|
setTimeout(triggerAutoSave, 100); // 等 DOM 更新后
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 预设项目勾选切换
|
|
||||||
panel.addEventListener('change', function(e) {
|
|
||||||
if (e.target.matches('.preset-check input[type="checkbox"]')) {
|
|
||||||
triggerAutoSave();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ================================================================
|
/* ================================================================
|
||||||
@@ -522,21 +509,27 @@
|
|||||||
|
|
||||||
if (dd) {
|
if (dd) {
|
||||||
totalDays++;
|
totalDays++;
|
||||||
var dayScore = 0;
|
|
||||||
var morning = dd.morning || [];
|
var morning = dd.morning || [];
|
||||||
var hasMorning = morning.some(function(x){ return x && typeof x === 'string' && x.trim(); });
|
|
||||||
var evening = dd.evening || [];
|
var evening = dd.evening || [];
|
||||||
var hasEvening = evening.some(function(x){
|
var study = dd.study || [];
|
||||||
|
|
||||||
|
var morningCount = morning.filter(function(x){ return x && typeof x === 'string' && x.trim(); }).length;
|
||||||
|
var eveningCount = evening.filter(function(x){
|
||||||
var mst = typeof x === 'string' ? x : (x.mistake || '');
|
var mst = typeof x === 'string' ? x : (x.mistake || '');
|
||||||
return mst && typeof mst === 'string' && mst.trim();
|
return mst && typeof mst === 'string' && mst.trim();
|
||||||
});
|
}).length;
|
||||||
var study = dd.study || [];
|
var studyCount = study.filter(function(x){ return x.done; }).length;
|
||||||
var hasStudy = study.some(function(x){ return x.done; });
|
|
||||||
if (hasMorning) dayScore++;
|
var allThree = morningCount > 0 && eveningCount > 0 && studyCount > 0;
|
||||||
if (hasEvening) dayScore++;
|
var dayScore, status;
|
||||||
if (hasStudy) dayScore++;
|
if (allThree) {
|
||||||
|
dayScore = 60 + (morningCount - 1 + eveningCount - 1 + studyCount - 1) * 5;
|
||||||
|
status = 'pass';
|
||||||
|
} else {
|
||||||
|
dayScore = (morningCount + eveningCount + studyCount) * 5;
|
||||||
|
status = 'fail';
|
||||||
|
}
|
||||||
totalScore += dayScore;
|
totalScore += dayScore;
|
||||||
var status = dayScore >= 2 ? 'pass' : 'fail';
|
|
||||||
|
|
||||||
dayCells.push({
|
dayCells.push({
|
||||||
ds: ds, dayScore: dayScore, status: status,
|
ds: ds, dayScore: dayScore, status: status,
|
||||||
@@ -550,21 +543,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var score = totalDays > 0 ? Math.round((totalScore / (totalDays * 3)) * 100) : 0;
|
var score = totalScore;
|
||||||
document.getElementById('weekly-score').textContent = score;
|
document.getElementById('weekly-score').textContent = score;
|
||||||
|
|
||||||
var ring = document.querySelector('.score-ring');
|
var ring = document.querySelector('.score-ring');
|
||||||
if (ring) {
|
if (ring) {
|
||||||
var R = 50, C = 2 * Math.PI * R;
|
var R = 50, C = 2 * Math.PI * R;
|
||||||
ring.setAttribute('stroke-dasharray', C);
|
ring.setAttribute('stroke-dasharray', C);
|
||||||
ring.setAttribute('stroke-dashoffset', C - (C * score / 100));
|
ring.setAttribute('stroke-dashoffset', C - (C * totalDays / 7));
|
||||||
}
|
}
|
||||||
|
|
||||||
var txt = '';
|
var txt = '';
|
||||||
if (score >= 90) txt = '卓越!磁场非常强大';
|
if (totalDays >= 7) txt = '全勤!本周每天都有记录';
|
||||||
else if (score >= 70) txt = '良好,继续保持';
|
else if (totalDays >= 5) txt = '良好,保持了大部分记录';
|
||||||
else if (score >= 50) txt = '一般,需要加强';
|
else if (totalDays >= 3) txt = '一般,需要更规律打卡';
|
||||||
else txt = '较弱,急需调整';
|
else txt = '本周记录偏少,加油!';
|
||||||
document.getElementById('score-text').textContent = txt;
|
document.getElementById('score-text').textContent = txt;
|
||||||
|
|
||||||
var completedDays = dayCells.filter(function(c){ return c.status === 'pass'; }).length;
|
var completedDays = dayCells.filter(function(c){ return c.status === 'pass'; }).length;
|
||||||
@@ -577,11 +570,11 @@
|
|||||||
var cell = dayCells[g];
|
var cell = dayCells[g];
|
||||||
var dateParts = cell.ds.split('-');
|
var dateParts = cell.ds.split('-');
|
||||||
var shortDate = dateParts[1] + '/' + dateParts[2];
|
var shortDate = dateParts[1] + '/' + dateParts[2];
|
||||||
var badgeText = cell.status === 'pass' ? '完成' : (cell.status === 'fail' ? '未达标' : '未打卡');
|
var badgeText = cell.status === 'pass' ? '达标' : (cell.status === 'fail' ? '未达标' : '未打卡');
|
||||||
gridHtml += '<div class="day-cell ' + cell.status + '">' +
|
gridHtml += '<div class="day-cell ' + cell.status + '">' +
|
||||||
'<div class="day-label">' + cell.weekday + '</div>' +
|
'<div class="day-label">' + cell.weekday + '</div>' +
|
||||||
'<div class="day-date">' + shortDate + '</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-score">' + cell.dayScore + '<span style="font-size:11px;font-weight:400">分</span></div>' +
|
||||||
'<div class="day-badge">' + badgeText + '</div>' +
|
'<div class="day-badge">' + badgeText + '</div>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -618,17 +618,14 @@ body {
|
|||||||
/* 责善改过 横排双输入 */
|
/* 责善改过 横排双输入 */
|
||||||
|
|
||||||
.evening-row {
|
.evening-row {
|
||||||
border: 1px solid var(--border);
|
padding: 0 0 12px;
|
||||||
border-radius: var(--radius-sm);
|
margin-bottom: 12px;
|
||||||
padding: 12px 10px 10px;
|
border-bottom: 1px solid var(--border);
|
||||||
margin-bottom: 10px;
|
|
||||||
background: var(--bg);
|
|
||||||
transition: border-color 0.2s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.evening-row:focus-within {
|
.evening-row:last-child {
|
||||||
border-color: var(--primary);
|
border-bottom: none;
|
||||||
box-shadow: 0 0 0 3px rgba(74,108,247,0.06);
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.evening-header {
|
.evening-header {
|
||||||
@@ -645,10 +642,10 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mistake-row {
|
.mistake-row {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: 1fr 1fr;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: start;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mistake-row .col {
|
.mistake-row .col {
|
||||||
@@ -664,7 +661,23 @@ body {
|
|||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mistake-row input[type="text"] { width: 100%; }
|
.mistake-row input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 9px 12px;
|
||||||
|
border: 1.5px solid var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: inherit;
|
||||||
|
color: var(--text);
|
||||||
|
background: var(--bg);
|
||||||
|
transition: border-color 0.2s, background 0.2s;
|
||||||
|
}
|
||||||
|
.mistake-row input[type="text"]:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary);
|
||||||
|
background: var(--card);
|
||||||
|
box-shadow: 0 0 0 3px rgba(74,108,247,0.08);
|
||||||
|
}
|
||||||
|
|
||||||
/* 勤学预设项目 */
|
/* 勤学预设项目 */
|
||||||
|
|
||||||
@@ -705,7 +718,7 @@ body {
|
|||||||
|
|
||||||
.preset-note {
|
.preset-note {
|
||||||
display: none;
|
display: none;
|
||||||
width: 100%;
|
width: calc(100% - 24px);
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
margin-left: 24px;
|
margin-left: 24px;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
@@ -1088,7 +1101,6 @@ body {
|
|||||||
.weekly-overview { flex-direction: column; gap: 16px; text-align: center; }
|
.weekly-overview { flex-direction: column; gap: 16px; text-align: center; }
|
||||||
.week-days-grid { grid-template-columns: repeat(4, 1fr); }
|
.week-days-grid { grid-template-columns: repeat(4, 1fr); }
|
||||||
.history-grid { grid-template-columns: 1fr; }
|
.history-grid { grid-template-columns: 1fr; }
|
||||||
.mistake-row { grid-template-columns: 1fr; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
|
|||||||
Reference in New Issue
Block a user