348e192800
Wire /learn static delivery, parent/child portal routes, and Tang page build scripts for 103 deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
103 lines
23 KiB
HTML
103 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
||
<title>我的学习小助手</title><style>*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
|
||
body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC",sans-serif;background:#f2f4fa;color:#1c2333;min-height:100vh;padding-bottom:70px;padding-bottom:calc(70px + constant(safe-area-inset-bottom));padding-bottom:calc(70px + env(safe-area-inset-bottom))}
|
||
.wrap{max-width:560px;margin:0 auto;padding:16px 14px}
|
||
header.h{padding:16px 2px 4px}
|
||
h1{font-size:22px;font-weight:700}
|
||
.sub{font-size:13px;color:#6b7280;margin-top:3px}
|
||
.card{background:#fff;border-radius:16px;padding:16px;margin-top:12px;box-shadow:0 1px 3px rgba(28,35,51,.06)}
|
||
.big{font-size:26px;font-weight:700}
|
||
.bar{height:10px;background:#e8ebf2;border-radius:5px;overflow:hidden;margin-top:8px}
|
||
.bar i{display:block;height:100%;background:linear-gradient(90deg,#22c55e,#16a34a);border-radius:5px;transition:width .4s}
|
||
.cat{margin-top:14px}
|
||
.cat-title{font-size:13px;font-weight:700;padding:3px 10px;border-radius:999px;display:inline-flex;align-items:center;gap:5px}
|
||
.task{display:flex;align-items:center;gap:12px;padding:12px 2px;border-bottom:1px solid #f0f2f7;cursor:pointer}
|
||
.task:last-child{border-bottom:none}
|
||
.task .ck{width:26px;height:26px;border-radius:50%;border:2px solid #cbd5e1;flex:none;display:flex;align-items:center;justify-content:center;font-size:15px;color:#fff}
|
||
.task.done .ck{background:#16a34a;border-color:#16a34a}
|
||
.task .info{flex:1;min-width:0}
|
||
.task .t{font-size:15px;font-weight:600}
|
||
.task.done .t{color:#9ca3af;text-decoration:line-through}
|
||
.task .d{font-size:12px;color:#6b7280;margin-top:2px}
|
||
.task .pt{font-size:12px;color:#f59e0b;font-weight:700;flex:none}
|
||
.tabbar{position:fixed;left:0;right:0;bottom:0 !important;top:auto !important;background:#fff;border-top:1px solid #e5e8f0;display:flex;z-index:20;padding:6px 0 8px;padding:6px 0 calc(8px + constant(safe-area-inset-bottom));padding:6px 0 calc(8px + env(safe-area-inset-bottom))}
|
||
.overlay{top:0 !important;left:0 !important;right:0 !important;bottom:0 !important}
|
||
.tabbar a{flex:1;text-align:center;text-decoration:none;color:#9ca3af;font-size:11px;padding:4px 0}
|
||
.tabbar a.on{color:#3b5bfd;font-weight:700}
|
||
.tabbar .ic{font-size:20px;display:block}
|
||
button{font-family:inherit;border:none;cursor:pointer;border-radius:12px;font-size:14px;font-weight:600;padding:10px 16px;background:#3b5bfd;color:#fff}
|
||
button:active{transform:scale(.97)}
|
||
button.ghost{background:#e8edff;color:#3b5bfd}
|
||
button.danger{background:#ffe9ee;color:#e11d48}
|
||
button:disabled{opacity:.4;cursor:not-allowed}
|
||
input,select,textarea{font-family:inherit;width:100%;border:2px solid #e5e8f0;border-radius:10px;padding:10px 12px;font-size:15px;outline:none;background:#fff;color:#1c2333}
|
||
input:focus,select:focus,textarea:focus{border-color:#3b5bfd}
|
||
label{display:block;font-size:13px;font-weight:600;margin:12px 0 5px}
|
||
.overlay{position:fixed;inset:0;background:rgba(20,24,38,.5);z-index:40;display:flex;align-items:center;justify-content:center;padding:20px}
|
||
.modal{background:#fff;border-radius:18px;padding:24px;width:100%;max-width:360px;max-height:85vh;overflow:auto}
|
||
.statcheck{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:12px}
|
||
.sc{background:#fff;border-radius:14px;padding:13px 8px;text-align:center;box-shadow:0 1px 3px rgba(28,35,51,.06)}
|
||
.sc .n{font-size:20px;font-weight:700}
|
||
.sc .l{font-size:11px;color:#6b7280;margin-top:2px}
|
||
.cal{display:grid;grid-template-columns:repeat(7,1fr);gap:6px;margin-top:10px}
|
||
.cal .w{text-align:center;font-size:11px;color:#9ca3af;padding:4px 0}
|
||
.cal .cell{text-align:center;padding:7px 0;border-radius:10px;font-size:14px;position:relative}
|
||
.cal .cell.has{font-weight:700}
|
||
.cal .cell.g{background:#e7f8ee;color:#16a34a}.cal .cell.y{background:#fff4dd;color:#d97706}.cal .cell.r{background:#ffe9ee;color:#e11d48}.cal .cell.n{color:#c3c9d6}
|
||
.cal .today{outline:2px solid #3b5bfd}
|
||
.legend{display:flex;gap:12px;font-size:11px;color:#6b7280;margin-top:10px;flex-wrap:wrap}
|
||
.dot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:4px;vertical-align:-1px}
|
||
.feed{position:fixed;left:50%;top:38%;transform:translate(-50%,-50%);background:#fff;border-radius:20px;padding:28px 30px;text-align:center;z-index:50;box-shadow:0 10px 40px rgba(28,35,51,.25);animation:pop .3s}
|
||
@keyframes pop{from{transform:translate(-50%,-50%) scale(.8);opacity:0}to{transform:translate(-50%,-50%) scale(1);opacity:1}}
|
||
.feed .em{font-size:44px}.feed .t1{font-size:18px;font-weight:700;margin-top:8px}.feed .t2{font-size:13px;color:#6b7280;margin-top:6px;line-height:1.6}
|
||
.feed .pt{color:#f59e0b;font-weight:700;margin-top:8px;font-size:15px}
|
||
table{width:100%;border-collapse:collapse;font-size:13px}
|
||
th,td{padding:8px 6px;border-bottom:1px solid #f0f2f7;text-align:left}
|
||
th{font-size:11px;color:#6b7280}
|
||
.row{display:flex;gap:10px;align-items:center}
|
||
.row>*{flex:1}
|
||
.pw{height:8px;background:#e8ebf2;border-radius:4px;overflow:hidden;min-width:60px}
|
||
.pw i{display:block;height:100%;background:#3b5bfd}
|
||
.chip{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:999px;border:2px solid #e5e8f0;font-size:13px;cursor:pointer;margin:0 8px 8px 0;background:#fff}
|
||
.chip.on{border-color:#3b5bfd;background:#e8edff;color:#3b5bfd;font-weight:700}
|
||
.hbtn{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:600;color:#3b5bfd;background:#e8edff;padding:6px 13px;border-radius:999px;text-decoration:none;cursor:pointer;flex:none;white-space:nowrap}
|
||
.hbtn:active{transform:scale(.95)}
|
||
.err{background:#ffe9ee;color:#e11d48;padding:10px;border-radius:10px;font-size:13px;margin-top:10px;display:none}
|
||
.tabs{display:flex;gap:8px;margin-top:14px}
|
||
.tabs button{flex:1;background:#e8ebf2;color:#6b7280}
|
||
.tabs button.on{background:#3b5bfd;color:#fff}
|
||
.empty{text-align:center;color:#9ca3af;font-size:13px;padding:18px 0;line-height:1.7}</style></head><body>
|
||
<div class="wrap" id="app"></div><div class="tabbar" id="tabbar"></div>
|
||
<div style="text-align:center;font-size:10px;color:#c3c9d6;padding:6px 0 76px">v2</div>
|
||
<script>
|
||
var FAMILY_SLUG=new URLSearchParams(location.search).get('f')||'tang';
|
||
var API_BASE='/api/learn/public/'+encodeURIComponent(FAMILY_SLUG);
|
||
var state={tab:'today',child:null,children:[],data:null,cal:null,grow:null};
|
||
function esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')}
|
||
async function api(p,opt){var r=await fetch(API_BASE+p,opt);var j=await r.json().catch(function(){return{}});if(!r.ok)throw new Error(j.error||'请求失败');return j}
|
||
function pickChild(){if(state.children.length<=1)return state.children[0]?state.children[0].id:1;var saved=localStorage.getItem('learn_child');var c=state.children.find(function(x){return String(x.id)===saved});if(!c){var s='<div class="card"><h1>我是谁呀?</h1>';state.children.forEach(function(x){s+='<div class="chip" style="margin-top:12px;font-size:16px;padding:12px 20px" onclick="chooseChild('+x.id+')">'+esc(x.emoji)+' '+esc(x.name)+'</div>'});s+='</div>';return s}return c.id}
|
||
function chooseChild(id){localStorage.setItem('learn_child',String(id));location.reload()}
|
||
function showSwitcher(){var s='<div class="overlay" onclick="if(event.target===this)this.remove()"><div class="modal"><h3 style="font-size:17px">👤 切换学员</h3><div style="margin-top:8px">';state.children.forEach(function(x){s+='<div class="chip" style="width:100%;justify-content:center;margin:8px 0 0;font-size:16px;padding:12px 20px'+(x.id==state.child?';border-color:#3b5bfd;background:#e8edff;font-weight:700':'')+'" onclick="chooseChild('+x.id+')">'+esc(x.emoji)+' '+esc(x.name)+(x.id==state.child?' ✓':'')+'</div>'});s+='</div><button class="ghost" style="margin-top:16px;width:100%" onclick="this.closest(\'.overlay\').remove()">取消</button></div></div>';document.body.insertAdjacentHTML('beforeend',s)}
|
||
function hdrActions(){var cur=state.children.find(function(x){return x.id==state.child});return '<div style="display:flex;gap:8px;flex:none;padding-top:6px"><span class="hbtn" onclick="showSwitcher()">⇄ '+(cur?'切换学员':'学员')+'</span><a class="hbtn" href="' + 'parent.html?f=' + FAMILY_SLUG + '">家长端</a></div>'}
|
||
function greet(){var h=new Date().getHours();return h<11?'早上好':h<14?'中午好':h<18?'下午好':'晚上好'}
|
||
function pad(n){return n<10?'0'+n:''+n}
|
||
function wdCN(d){return ['日','一','二','三','四','五','六'][d]}
|
||
function dowOf(ds){return new Date(Date.UTC(+ds.slice(0,4),+ds.slice(5,7)-1,+ds.slice(8,10))).getUTCDay()}
|
||
function renderToday(){var d=state.data;var p=d.total?Math.round(d.done/d.total*100):0;var s='<header class="h"><div style="display:flex;justify-content:space-between;align-items:flex-start;gap:10px"><div><h1>'+greet()+','+esc(d.child.name)+' '+esc(d.child.emoji)+'</h1><div class="sub">'+d.date.replace(/-/g,'/')+' 周'+wdCN(dowOf(d.date))+'</div></div>'+hdrActions()+'</div></header><div class="card"><div class="row" style="align-items:baseline"><div><div class="big">'+d.done+' / '+d.total+'</div><div class="sub">今天已完成</div></div><div style="text-align:right"><div class="big" style="color:#f59e0b">⭐ '+d.energy+'</div><div class="sub">我的能量</div></div></div><div class="bar"><i style="width:'+p+'%"></i></div>'+(d.total&&d.done===d.total?'<div style="text-align:center;margin-top:10px;font-size:15px;font-weight:700;color:#16a34a">⭐ 今天全部完成,太棒啦!</div>':'')+'</div>';var byCat={},order=[];d.tasks.forEach(function(t){var k=t.category_name||'其他';if(!byCat[k]){byCat[k]={color:t.category_color||'#6b7280',list:[]};order.push(k)}byCat[k].list.push(t)});if(!d.tasks.length)s+='<div class="card"><div class="empty">今天没有任务<br>好好休息,或找爸爸妈妈安排~</div></div>';order.forEach(function(k){s+='<div class="cat"><span class="cat-title" style="background:'+byCat[k].color+'1a;color:'+byCat[k].color+'">'+esc(k)+'</span><div class="card">';byCat[k].list.forEach(function(t){var needVal=(t.task_type!=='check');var sub=[];if(t.description)sub.push(esc(t.description));if(needVal)sub.push('目标:'+esc(t.target_value||'')+(t.unit?esc(t.unit):''));if(t.start_date)sub.push('📅 '+t.start_date.replace(/-/g,'/')+' 开始');if(t.status==='done'){sub.unshift('✓ '+(t.completed_time||'')+' 完成'+(needVal&&t.value_submitted?','+esc(t.value_submitted)+(t.unit?esc(t.unit):''):''));}s+='<div class="task'+(t.status==='done'?' done':'')+'" onclick="doTask('+t.id+')"><div class="ck"'+(t.status==='pending'?' style="border-color:#e11d48"':'')+'>'+(t.status==='done'?'✓':'')+'</div><div class="info"><div class="t">'+esc(t.title)+'</div>'+(sub.length?'<div class="d">'+sub.join('<br>')+'</div>':'')+'</div><div class="pt">+'+t.points+'</div></div>'});s+='</div></div>'});if(d.streak>0)s+='<div class="card" style="text-align:center"><span style="font-size:15px">🔥 连续打卡 <b style="color:#f59e0b">'+d.streak+'</b> 天,继续保持!</span></div>';return s}
|
||
function renderCal(){var m=state.cal.month;var first=m+'-01';var dim=new Date(Date.UTC(+m.slice(0,4),+m.slice(5,7),0)).getUTCDate();var lead=dowOf(first);var map={};state.cal.days.forEach(function(x){map[x.date]=x});var s='<header class="h"><div style="display:flex;justify-content:space-between;align-items:flex-start;gap:10px"><div><h1>我的日历 📅</h1><div class="sub">'+m.replace('-',' 年 ')+' 月</div></div>'+hdrActions()+'</div></header><div class="card"><div style="display:flex;justify-content:space-between;align-items:center"><button class="ghost" style="padding:6px 14px" onclick="calShift(-1)">← 上月</button><button class="ghost" style="padding:6px 14px" onclick="calShift(1)">下月 →</button></div><div class="cal">';['日','一','二','三','四','五','六'].forEach(function(w){s+='<div class="w">'+w+'</div>'});for(var i=0;i<lead;i++)s+='<div></div>';var today=new Date(Date.now()+8*3600e3).toISOString().slice(0,10);for(var d=1;d<=dim;d++){var ds=m+'-'+pad(d);var st=map[ds];var cls='cell ';var mark='';if(st){cls+=st.status==='green'?'g':st.status==='yellow'?'y':st.status==='red'?'r':'n';if(st.total){cls+=' has';if(st.status==='red')mark='<i style="position:absolute;top:4px;right:5px;width:7px;height:7px;border-radius:50%;background:#e11d48;box-shadow:0 0 4px #e11d48"></i>';else if(st.status==='yellow')mark='<i style="position:absolute;top:4px;right:5px;width:7px;height:7px;border-radius:50%;background:#f59e0b"></i>';}}if(ds===today)cls+=' today';s+='<div class="'+cls+'" style="position:relative;'+(st&&st.status==='red'?'box-shadow:0 0 0 2px #e11d48 inset':'')+'" onclick="showDay(\''+ds+'\')">'+d+mark+'</div>'}s+='</div><div class="legend"><span><i class="dot" style="background:#16a34a"></i>全部完成</span><span><i class="dot" style="background:#f59e0b"></i>部分完成</span><span><i class="dot" style="background:#e11d48"></i>有遗漏(红点)</span><span><i class="dot" style="background:#c3c9d6"></i>无任务</span><div style="width:100%;text-align:center;margin-top:4px;color:#3b5bfd">点任意日期查看当天详情</div></div></div><div id="dayDetail"></div>';return s}
|
||
async function showDay(ds){var j=await api('/day?child='+state.child+'&date='+ds);var el=document.getElementById('dayDetail');var isToday=ds===new Date(Date.now()+8*3600e3).toISOString().slice(0,10);var stTxt='';var stColor='';var done=j.tasks.filter(function(t){return t.status==='done'}).length;if(j.tasks.length){if(done===j.tasks.length){stTxt='全部完成 ✓';stColor='#16a34a'}else if(done>0){stTxt='完成 '+done+'/'+j.tasks.length+(ds<new Date(Date.now()+8*3600e3).toISOString().slice(0,10)?',有遗漏':'');stColor=ds<new Date(Date.now()+8*3600e3).toISOString().slice(0,10)?'#e11d48':'#d97706'}else{stTxt=ds<new Date(Date.now()+8*3600e3).toISOString().slice(0,10)?'全部未完成 ✕':'待完成';stColor=ds<new Date(Date.now()+8*3600e3).toISOString().slice(0,10)?'#e11d48':'#d97706'}}var s='<div class="card"><div class="row" style="align-items:center;margin-bottom:6px"><b style="font-size:15px;flex:1">'+ds.replace(/-/g,'/')+' 周'+wdCN(dowOf(ds))+(isToday?'(今天)':'')+'</b>'+(stTxt?'<span style="font-size:12px;font-weight:700;color:'+stColor+'">'+stTxt+'</span>':'')+'</div>';if(!j.tasks.length)s+='<div class="empty">那天没有任务</div>';j.tasks.forEach(function(t){var overdue=t.status==='pending'&&ds<new Date(Date.now()+8*3600e3).toISOString().slice(0,10);s+='<div class="task'+(t.status==='done'?' done':'')+'"><div class="ck"'+(overdue?' style="border-color:#e11d48;color:#e11d48;font-size:12px"':'')+'>'+(t.status==='done'?'✓':(overdue?'✕':''))+'</div><div class="info"><div class="t">'+esc(t.title)+'</div><div class="d"'+((overdue)?' style="color:#e11d48"':'')+'>'+esc(t.category_name||'')+(t.status==='done'?' · '+(t.completed_time||'')+' 完成':(overdue?' · 未完成':' · 待完成')+(t.value_submitted?' · '+esc(t.value_submitted)+(t.unit||''):''))+'</div></div></div>'});if(j.tasks.length)s+='<div class="sub" style="margin-top:10px">完成率:'+Math.round(done/j.tasks.length*100)+'%</div>';s+='</div>';el.innerHTML=s}
|
||
function renderGrow(){var g=state.grow;var s='<header class="h"><div style="display:flex;justify-content:space-between;align-items:flex-start;gap:10px"><div><h1>我的成长 🌱</h1></div>'+hdrActions()+'</div></header><div class="statcheck"><div class="sc"><div class="n" style="color:#f59e0b">⭐ '+g.energy+'</div><div class="l">我的能量</div></div><div class="sc"><div class="n" style="color:#ef4444">🔥 '+g.streak+'</div><div class="l">连续打卡</div></div><div class="sc"><div class="n">'+g.totalDone+'</div><div class="l">累计完成</div></div></div><div class="card"><div class="sub" style="margin-bottom:6px">💪 历史最长连续:<b>'+g.best+'</b> 天 | 全勤天数:<b>'+g.fullDays+'</b> 天</div></div><div class="card"><div class="t" style="font-weight:700;font-size:15px;margin-bottom:8px">近 30 天分类表现</div>';if(!g.byCategory.length)s+='<div class="empty">还没有记录,快去完成任务吧!</div>';g.byCategory.forEach(function(c){var p=c.total?Math.round(c.done/c.total*100):0;s+='<div style="margin:10px 0"><div class="row" style="align-items:center"><span style="flex:0 0 64px;font-size:13px;font-weight:600">'+esc(c.name)+'</span><div class="pw" style="flex:1"><i style="width:'+p+'%"></i></div><span style="flex:0 0 40px;text-align:right;font-size:12px;color:#6b7280">'+p+'%</span></div></div>'});s+='</div>';return s}
|
||
function renderHist(){var h=state.hist;var today=new Date(Date.now()+8*3600e3).toISOString().slice(0,10);var s='<header class="h"><div style="display:flex;justify-content:space-between;align-items:flex-start;gap:10px"><div><h1>我的记录 📖</h1><div class="sub">每天的任务和完成情况,都记在这里'+(h.cycleStart?'<br>本期任务从 <b style="color:#3b5bfd">'+h.cycleStart.replace(/-/g,'/')+'</b> 开始':'')+'</div></div>'+hdrActions()+'</div></header>';if(!h.data.length)s+='<div class="card"><div class="empty">还没有记录<br>完成今天的任务,就会出现在这里哦</div></div>';var daysAsc=h.data.slice().reverse();daysAsc.forEach(function(day,idx){var label=day.date===today?'今天':(day.date===addDaysLocal(today,-1)?'昨天':(parseInt(day.date.slice(5),10))+'月'+parseInt(day.date.slice(8),10)+'日');var w='周'+wdCN(dowOf(day.date));var stTxt=day.status==='green'?'全部完成':(day.done>0?'完成 '+day.done+'/'+day.total:(day.date<today?'有遗漏':'待完成'));var stColor=day.status==='green'?'#16a34a':day.status==='red'?'#e11d48':'#d97706';var stBg=day.status==='green'?'#e7f8ee':day.status==='red'?'#ffe9ee':'#fff4dd';var open=day.date===today;var bodyId='hb'+idx;var dot=day.status==='green'?'🟢':(day.status==='red'?'🔴':'🟡');s+='<div class="card"><div class="row" style="align-items:center;cursor:pointer" onclick="toggleDay(\''+bodyId+'\',this)"><div style="flex:1;cursor:pointer"><span style="font-size:16px;font-weight:700;cursor:pointer">'+label+'</span> <span class="sub">'+w+' '+dot+'</span></div><span style="font-size:12px;font-weight:700;color:'+stColor+';background:'+stBg+';padding:3px 10px;border-radius:999px;cursor:pointer">'+stTxt+'</span><span style="flex:0 0 16px;text-align:right;color:#9ca3af;font-size:12px;cursor:pointer" class="arr">'+(open?'▾':'▸')+'</span></div><div id="'+bodyId+'" style="'+(open?'':'display:none')+';margin-top:4px">';day.tasks.forEach(function(t){var sub=[];if(t.status==='done')sub.push('✓ '+(t.completed_time||'')+' 完成'+(t.task_type!=='check'&&t.value_submitted?','+esc(t.value_submitted)+(t.unit?esc(t.unit):''):''));else if(t.status==='pending'&&day.date<today)sub.push('✕ 未完成');else sub.push('待完成');if(t.remark)sub.push('📝 '+esc(t.remark));s+='<div class="task'+(t.status==='done'?' done':'')+'"><div class="ck">'+(t.status==='done'?'✓':(t.status==='pending'&&day.date<today?'✕':''))+'</div><div class="info"><div class="t">'+esc(t.title)+'</div><div class="d">'+esc(t.category_name||'')+' · '+sub.join(' · ')+'</div></div></div>'});s+='</div></div>'});return s}
|
||
function toggleDay(id,el){var b=document.getElementById(id);var arr=el.querySelector('.arr')||el.parentElement.querySelector('.arr');if(b.style.display==='none'){b.style.display='block';if(arr)arr.textContent='▾'}else{b.style.display='none';if(arr)arr.textContent='▸'}}
|
||
function addDaysLocal(ds,n){var d=new Date(ds+'T00:00:00Z');d.setUTCDate(d.getUTCDate()+n);return d.toISOString().slice(0,10)}
|
||
function renderTabbar(){var t=[['today','☀️','今日'],['hist','📖','记录'],['cal','📅','日历'],['grow','🌱','成长']];return t.map(function(x){return '<a href="javascript:void(0)" class="'+(state.tab===x[0]?'on':'')+'" onclick="go(\''+x[0]+'\')"><span class="ic">'+x[1]+'</span>'+x[2]+'</a>'}).join('')}
|
||
async function go(tab){state.tab=tab;await load();render()}
|
||
async function load(){if(!state.children.length){state.children=(await api('/children')).data}if(state.child==null){var c=pickChild();if(typeof c!=='number')return;state.child=c}if(state.child==null)return;if(state.tab==='today')state.data=await api('/today?child='+state.child);if(state.tab==='hist')state.hist=await api('/history?child='+state.child);if(state.tab==='cal'&&!state.cal)state.cal=await api('/calendar?child='+state.child);if(state.tab==='grow')state.grow=await api('/growth?child='+state.child)}
|
||
function render(){var app=document.getElementById('app');if(!state.children.length){app.innerHTML='<div class="card"><div class="empty">还没有小朋友的信息<br>请爸爸妈妈先去 <a href="' + 'parent.html?f=' + FAMILY_SLUG + '" style="color:#3b5bfd">家长后台</a> 添加</div></div>';document.getElementById('tabbar').innerHTML='';return}if(state.child==null){app.innerHTML=pickChild();document.getElementById('tabbar').innerHTML='';return}var s=state.tab==='today'?renderToday():state.tab==='hist'?renderHist():state.tab==='cal'?renderCal():renderGrow();app.innerHTML=s;document.getElementById('tabbar').innerHTML=renderTabbar()}
|
||
async function calShift(n){var m=state.cal.month.split('-');var y=+m[0],mo=+m[1]+n;if(mo<1){mo=12;y--}if(mo>12){mo=1;y++}state.cal=await api('/calendar?child='+state.child+'&month='+y+'-'+pad(mo));render()}
|
||
function doTask(id){var t=null;state.data.tasks.forEach(function(x){if(x.id===id)t=x});if(!t)(state.data.pastPending||[]).forEach(function(x){if(x.id===id)t=x});if(!t)return;if(t.status==='done')return;if(t.task_type==='check'){submit(id)}else{var unit=t.unit||'';var label=t.task_type==='count'?'完成了多少'+(unit?esc(unit):''):(t.task_type==='duration'?'坚持了多少'+(unit||'分钟'):'得了多少分');var def=t.value_submitted||(t.task_type==='duration'?(t.target_value||''):(t.target_value||''));document.getElementById('app').insertAdjacentHTML('beforeend','<div class="overlay" onclick="if(event.target===this)this.remove()"><div class="modal"><h3 style="font-size:17px">✏️ '+esc(t.title)+'</h3><label>'+label+'</label><input id="vInput" inputmode="numeric" value="'+esc(def)+'" placeholder="填一个数字吧"><label>想说的悄悄话(选填)</label><textarea id="vRemark" rows="2" style="font-size:14px"></textarea><div class="row" style="margin-top:16px"><button class="ghost" onclick="this.closest(\'.overlay\').remove()">再等等</button><button onclick="submitVal('+id+')">✓ 完成打卡</button></div></div></div>');var inp=document.getElementById('vInput');if(inp)inp.focus()}}
|
||
function submitVal(id){var v=document.getElementById('vInput').value;var r=document.getElementById('vRemark').value;submit(id,v,r)}
|
||
async function submit(id,value,remark){try{var j=await api('/checkin',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:id,value:value,remark:remark})});var s='<div class="feed" onclick="this.remove()"><div class="em">🎉</div><div class="t1">完成啦!</div><div class="t2">今天已经完成 <b>'+j.done+'</b> / '+j.total+' 项<br>'+(j.allDone?'⭐ 太棒了,今天全部完成!':'再完成 '+(j.total-j.done)+' 项,今天就是全绿!')+'</div><div class="pt">+'+j.points+' 能量</div><div class="t2" style="margin-top:12px;font-size:11px;color:#c3c9d6">点一下继续</div></div>';document.body.insertAdjacentHTML('beforeend',s);await load();render()}catch(e){alert(e.message)}}
|
||
(async function(){try{await load();render()}catch(e){document.getElementById('app').innerHTML='<div class="card"><div class="empty">'+esc(e.message)+'</div></div>'}})();
|
||
</script></body></html> |