初始化 ziwei-power:紫微磁场管理系统(Flask + SQLite + 登录鉴权)
This commit is contained in:
+411
@@ -0,0 +1,411 @@
|
||||
/* 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,'&').replace(/"/g,'"').replace(/</g,'<').replace(/>/g,'>');
|
||||
}
|
||||
|
||||
/* ── 初始化 ────────────────────────────── */
|
||||
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();
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,313 @@
|
||||
:root {
|
||||
--primary: #4A6CF7;
|
||||
--primary-light: #EEF1FE;
|
||||
--danger: #EF4444;
|
||||
--danger-light: #FEF2F2;
|
||||
--bg: #F5F7FB;
|
||||
--card: #FFFFFF;
|
||||
--text: #1F2937;
|
||||
--text-muted: #9CA3AF;
|
||||
--border: #E5E7EB;
|
||||
--radius: 12px;
|
||||
--shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
* { margin:0; padding:0; box-sizing:border-box; }
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
padding: 16px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
header {
|
||||
text-align: center;
|
||||
padding: 20px 0 12px;
|
||||
}
|
||||
.header-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
header h1 { font-size: 22px; font-weight: 700; color: var(--primary); }
|
||||
header p { font-size: 13px; color: var(--text-muted); margin-top: 4px; }
|
||||
.user-bar {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.user-name {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
.btn-logout {
|
||||
font-size: 11px;
|
||||
color: var(--danger);
|
||||
text-decoration: none;
|
||||
padding: 2px 8px;
|
||||
border: 1px solid var(--danger);
|
||||
border-radius: 4px;
|
||||
transition: all .2s;
|
||||
}
|
||||
.btn-logout:hover {
|
||||
background: var(--danger);
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
/* ── Tabs ── */
|
||||
.tabs {
|
||||
display: flex;
|
||||
background: var(--card);
|
||||
border-radius: var(--radius);
|
||||
padding: 4px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.tab {
|
||||
flex: 1;
|
||||
padding: 10px 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
border-radius: 10px;
|
||||
color: var(--text-muted);
|
||||
transition: all .2s;
|
||||
}
|
||||
.tab.active {
|
||||
background: var(--primary);
|
||||
color: #FFF;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ── Tab content ── */
|
||||
.tab-content { display: none; }
|
||||
.tab-content.active { display: block; }
|
||||
|
||||
/* ── Date row ── */
|
||||
.date-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.date-row input[type="date"] {
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
/* ── Card ── */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: var(--radius);
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.card h2 { font-size: 15px; margin-bottom: 2px; }
|
||||
.card-desc { font-size: 12px; color: var(--text-muted); margin-bottom: 12px; }
|
||||
|
||||
/* ── Dynamic items ── */
|
||||
.item-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.item-row input[type="text"],
|
||||
.item-row textarea {
|
||||
flex:1;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-family: inherit;
|
||||
resize: vertical;
|
||||
}
|
||||
.item-row textarea { min-height: 40px; }
|
||||
.item-row .idx {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
min-width: 20px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.btn-del {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--danger);
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
padding: 6px 4px;
|
||||
line-height: 1;
|
||||
}
|
||||
.btn-del:hover { opacity: 0.7; }
|
||||
|
||||
/* 责善改过 双输入 */
|
||||
.mistake-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
}
|
||||
.mistake-group .label-hint {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
/* ── Study items ── */
|
||||
.study-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.study-row input[type="text"] {
|
||||
flex: 1;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-family: inherit;
|
||||
}
|
||||
.study-row input[type="checkbox"] {
|
||||
width: 18px; height: 18px;
|
||||
accent-color: var(--primary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── Buttons ── */
|
||||
.btn-add {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-top: 8px;
|
||||
border: 1px dashed var(--primary);
|
||||
background: var(--primary-light);
|
||||
color: var(--primary);
|
||||
font-size: 13px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all .2s;
|
||||
}
|
||||
.btn-add:hover { background: #DDE3FD; }
|
||||
|
||||
.btn-save {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
margin-top: 8px;
|
||||
border: none;
|
||||
background: var(--primary);
|
||||
color: #FFF;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
transition: all .2s;
|
||||
}
|
||||
.btn-save:hover { opacity: 0.9; transform: translateY(-1px); }
|
||||
|
||||
/* ── Week nav ── */
|
||||
.week-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
padding: 16px 0;
|
||||
}
|
||||
.btn-nav {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--card);
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#week-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
min-width: 140px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ── Score ── */
|
||||
.score-display { text-align: center; padding: 20px 0 8px; }
|
||||
.score-num { font-size: 56px; font-weight: 800; color: var(--primary); }
|
||||
.score-unit { font-size: 20px; color: var(--text-muted); margin-left: 4px; }
|
||||
.score-text { text-align: center; font-size: 14px; margin-bottom: 16px; }
|
||||
|
||||
/* ── Details ── */
|
||||
.details-list { margin-top: 8px; }
|
||||
.details-list .detail-row {
|
||||
padding: 10px 16px;
|
||||
background: var(--card);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 6px;
|
||||
font-size: 13px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
/* ── History ── */
|
||||
#history-list { margin-top: 4px; }
|
||||
.history-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 16px;
|
||||
background: var(--card);
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.history-row .info { flex: 1; }
|
||||
.history-row .info .date { font-weight: 600; font-size: 14px; color: var(--primary); }
|
||||
.history-row .info .preview { font-size: 12px; color: var(--text-muted); margin-top: 4px; }
|
||||
.history-row .btn-del-history {
|
||||
background: var(--danger-light);
|
||||
border: none;
|
||||
color: var(--danger);
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
margin-left: 12px;
|
||||
}
|
||||
.history-row .btn-del-history:hover { opacity: 0.8; }
|
||||
|
||||
/* ── Toast ── */
|
||||
.toast {
|
||||
position: fixed;
|
||||
top: 16px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
z-index: 999;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity .3s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.toast.show { opacity: 1; pointer-events: auto; }
|
||||
.toast.success { background: #10B981; color: #FFF; }
|
||||
.toast.error { background: var(--danger); color: #FFF; }
|
||||
.toast.info { background: var(--primary); color: #FFF; }
|
||||
Reference in New Issue
Block a user