#!/usr/bin/env node
/**
* 生成唐用户学习小助手 HTML(儿童端 + 家长端)
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createLearnAssistantService, learnAssistantInternals } from '../learn-assistant-service.mjs';
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
const TANG = learnAssistantInternals.TANG_OWNER_ID;
const API_BASE = '/api/learn/tang';
const outDir = path.join(root, 'MindSpace', TANG, 'public');
const sharedCss = `*{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}
[data-mindspace-public-share],[data-mindspace-wechat-dialog],.publication-share-fab,.publication-share-sheet{display:none!important;visibility:hidden!important}`;
function childScript() {
return `
var API_BASE='${API_BASE}';
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,'>')}
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='
我是谁呀? ';state.children.forEach(function(x){s+='
'+esc(x.emoji)+' '+esc(x.name)+'
'});s+='
';return s}return c.id}
function chooseChild(id){localStorage.setItem('learn_child',String(id));location.reload()}
function showSwitcher(){var s='👤 切换学员 ';state.children.forEach(function(x){s+='
'+esc(x.emoji)+' '+esc(x.name)+(x.id==state.child?' ✓':'')+'
'});s+='
取消 ';document.body.insertAdjacentHTML('beforeend',s)}
function hdrActions(){var cur=state.children.find(function(x){return x.id==state.child});return '⇄ '+(cur?'切换学员':'学员')+' 家长端 '}
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=''+greet()+','+esc(d.child.name)+' '+esc(d.child.emoji)+' '+d.date.replace(/-/g,'/')+' 周'+wdCN(dowOf(d.date))+'
'+hdrActions()+'
'+d.done+' / '+d.total+'
今天已完成
'+(d.total&&d.done===d.total?'
⭐ 今天全部完成,太棒啦!
':'')+'
';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+='';order.forEach(function(k){s+=''+esc(k)+' ';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+='
'+(t.status==='done'?'✓':'')+'
'+esc(t.title)+'
'+(sub.length?'
'+sub.join(' ')+'
':'')+'
+'+t.points+'
'});s+='
'});if(d.streak>0)s+='🔥 连续打卡 '+d.streak+' 天,继续保持!
';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='我的日历 📅 '+m.replace('-',' 年 ')+' 月
'+hdrActions()+'
← 上月 下月 →
';['日','一','二','三','四','五','六'].forEach(function(w){s+='
'+w+'
'});for(var i=0;i
';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='
';else if(st.status==='yellow')mark='
';}}if(ds===today)cls+=' today';s+='
'+d+mark+'
'}s+='
全部完成 部分完成 有遗漏(红点) 无任务点任意日期查看当天详情
';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'+(stTxt?''+stTxt+' ':'')+'';if(!j.tasks.length)s+='那天没有任务
';j.tasks.forEach(function(t){var overdue=t.status==='pending'&&ds'+(t.status==='done'?'✓':(overdue?'✕':''))+'
'+esc(t.title)+'
'+esc(t.category_name||'')+(t.status==='done'?' · '+(t.completed_time||'')+' 完成':(overdue?' · 未完成':' · 待完成')+(t.value_submitted?' · '+esc(t.value_submitted)+(t.unit||''):''))+'
'});if(j.tasks.length)s+='完成率:'+Math.round(done/j.tasks.length*100)+'%
';s+='';el.innerHTML=s}
function renderGrow(){var g=state.grow;var s='💪 历史最长连续:'+g.best+' 天 | 全勤天数:'+g.fullDays+' 天
近 30 天分类表现
';if(!g.byCategory.length)s+='
还没有记录,快去完成任务吧!
';g.byCategory.forEach(function(c){var p=c.total?Math.round(c.done/c.total*100):0;s+='
'});s+='
';return s}
function renderHist(){var h=state.hist;var today=new Date(Date.now()+8*3600e3).toISOString().slice(0,10);var s='我的记录 📖 每天的任务和完成情况,都记在这里'+(h.cycleStart?' 本期任务从 '+h.cycleStart.replace(/-/g,'/')+' 开始':'')+'
'+hdrActions()+'
';if(!h.data.length)s+='';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'+label+' '+w+' '+dot+'
'+stTxt+' '+(open?'▾':'▸')+' ';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
'+(t.status==='done'?'✓':(t.status==='pending'&&day.date
'+esc(t.title)+'
'+esc(t.category_name||'')+' · '+sub.join(' · ')+'
'});s+=' '});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 ''+x[1]+' '+x[2]+' '}).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='';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','');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='🎉
完成啦!
今天已经完成 '+j.done+' / '+j.total+' 项 '+(j.allDone?'⭐ 太棒了,今天全部完成!':'再完成 '+(j.total-j.done)+' 项,今天就是全绿!')+'
+'+j.points+' 能量
点一下继续
';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=''}})();`;
}
function parentScript() {
return `
var API_BASE='${API_BASE}';
var state={pw:localStorage.getItem('learn_pw')||'',tab:'dash',stats:null,templates:null,categories:null,children:null,editId:null};
function esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(//g,'>')}
async function api(p,opt){opt=opt||{};opt.headers=Object.assign({'X-Parent-Pw':state.pw,'X-Admin-Pw':state.pw,'Content-Type':'application/json'},opt.headers||{});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 pad(n){return n<10?'0'+n:''+n}
var WD=['一','二','三','四','五','六','日'];
function weekdaysCN(w){return w.split(',').map(function(x){return WD[+x-1]}).join('、')}
var TYPE={check:'完成型',count:'数量型',duration:'时长型',score:'成绩型'};
function renderLogin(){return ''}
async function login(){state.pw=document.getElementById('pw').value;try{await api('/parent/stats');localStorage.setItem('learn_pw',state.pw);state.tab='dash';await loadAll();render()}catch(e){var el=document.getElementById('err');el.style.display='block';el.textContent=e.message}}
function renderDash(){var s=state.stats;var tp=s.today.total?Math.round(s.today.done/s.today.total*100):0;var wp=s.week.total?Math.round(s.week.done/s.week.total*100):0;var st=s.children.length?s.children[0].streak:0;var html=''+tp+'%
今日完成率 '+(s.today.done+'/'+s.today.total)+'
近 7 天趋势
';s.trend.forEach(function(d){var p=d.total?Math.round(d.done/d.total*100):0;html+='
'});html+='
';if(s.missed.length){html+='⚠️ 近 30 天容易遗漏
';s.missed.forEach(function(m){html+=''+esc(m.title)+' 漏 '+m.count+' 次 '});html+='
'}if(s.categoryRate.length){html+='📚 分类完成率(近 30 天)
';s.categoryRate.forEach(function(c){var p=c.total?Math.round(c.done/c.total*100):0;html+='
'});html+='
'}if(s.avgTime.length){html+='⏰ 平均完成时间(近 30 天)
';s.avgTime.forEach(function(a){html+=''+esc(a.name)+' '+a.time+' '});html+='
提示:越晚完成的科目,遗漏概率往往越高
'}return html}
function renderTemplates(){var html='任务模板 📋 配置周期规则,系统每天自动生成当日任务
';if(!state.templates.length)html+='';state.templates.forEach(function(t){html+=''+esc(t.title)+'
'+(t.category_name?'【'+esc(t.category_name)+'】 ':'')+TYPE[t.task_type]+(t.target_value?' · '+esc(t.target_value)+(t.unit?esc(t.unit):''):'')+' 每周:'+weekdaysCN(t.weekdays)+' | 从 '+String(t.start_date).slice(0,10)+' 起'+(t.end_date?' 至 '+String(t.end_date).slice(0,10):'(长期)')+' +'+t.points+' 能量'+(t.description?' 要求:'+esc(t.description):'')+'
编辑 停用
'});html+='+ 新建任务模板 ';return html}
function tplForm(t){var kids=state.children.map(function(c){return ''+esc(c.name)+' '}).join('');var cats=state.categories.map(function(c){return ''+esc(c.name)+' '}).join('');var wds=(t?t.weekdays:'1,2,3,4,5').split(',');var wchk=WD.map(function(w,i){return ''+w+' '}).join('');return ''}
function newTpl(){document.getElementById('app').insertAdjacentHTML('beforeend',tplForm(null))}
function editTpl(id){var t=state.templates.find(function(x){return x.id===id});if(t)document.getElementById('app').insertAdjacentHTML('beforeend',tplForm(t))}
async function delTpl(id,name){if(!confirm('停用「'+name+'」?已生成的历史任务保留,今后不再生成。'))return;await api('/parent/templates/'+id,{method:'DELETE'});await loadAll();render()}
async function saveTpl(id){var wds=[];document.querySelectorAll('.overlay .chip.on').forEach(function(c){wds.push(String(WD.indexOf(c.textContent)+1))});var body={child_id:+document.getElementById('f_child').value,category_id:+document.getElementById('f_cat').value||null,title:document.getElementById('f_title').value,description:document.getElementById('f_desc').value,task_type:document.getElementById('f_type').value,target_value:document.getElementById('f_target').value,unit:document.getElementById('f_unit').value,weekdays:wds,start_date:document.getElementById('f_start').value,end_date:document.getElementById('f_end').value,points:+document.getElementById('f_points').value};try{if(!body.title)throw new Error('任务名称必填');if(!wds.length)throw new Error('请至少选择一个星期');if(id)await api('/parent/templates/'+id,{method:'PUT',body:JSON.stringify(body)});else await api('/parent/templates',{method:'POST',body:JSON.stringify(body)});document.querySelector('.overlay').remove();await loadAll();render()}catch(e){var el=document.getElementById('f_err');el.style.display='block';el.textContent=e.message}}
function renderCats(){var html='';state.categories.forEach(function(c){html+=' '+esc(c.name)+'删除 '});html+='
';return html}
async function addCat(){var n=document.getElementById('c_name').value.trim();if(!n)return;await api('/parent/categories',{method:'POST',body:JSON.stringify({name:n,color:document.getElementById('c_color').value})});await loadAll();render()}
async function delCat(id,name){if(!confirm('删除分类「'+name+'」?使用它的模板将变为无分类。'))return;await api('/parent/categories/'+id,{method:'DELETE'});await loadAll();render()}
function renderKids(){var html='';state.children.forEach(function(c){html+=''+esc(c.emoji)+' '+esc(c.name)+'
删除 '});html+='';return html}
async function addKid(){var n=document.getElementById('k_name').value.trim();if(!n)return;await api('/parent/children',{method:'POST',body:JSON.stringify({name:n,emoji:document.getElementById('k_emoji').value||'🙂'})});await loadAll();render()}
async function delKid(id,name){if(!confirm('删除「'+name+'」及其所有任务记录?此操作不可恢复!'))return;await api('/parent/children/'+id,{method:'DELETE'});await loadAll();render()}
function renderSet(){return '· 儿童端地址:
打开 · 任务每天首次访问时自动生成
· 历史任务保存快照,改模板不影响过去记录
· 数据存储:Memind Portal
退出登录 '}
async function chpw(){try{await api('/parent/password',{method:'POST',body:JSON.stringify({old:document.getElementById('p_old').value,new:document.getElementById('p_new').value})});alert('密码已更新');state.pw=document.getElementById('p_new').value;localStorage.setItem('learn_pw',state.pw)}catch(e){var el=document.getElementById('p_err');el.style.display='block';el.textContent=e.message}}
function logout(){localStorage.removeItem('learn_pw');state.pw='';render()}
async function loadAll(){if(state.tab==='dash')state.stats=await api('/parent/stats');if(state.tab==='tpl'){state.templates=(await api('/parent/templates')).data;state.categories=(await api('/parent/categories')).data;state.children=(await api('/parent/children')).data}if(state.tab==='cat'){state.categories=(await api('/parent/categories')).data}if(state.tab==='kid'){state.children=(await api('/parent/children')).data}}
function render(){var app=document.getElementById('app');if(!state.pw){app.innerHTML=renderLogin();return}var tabs=[['dash','📊','看板'],['tpl','📋','模板'],['cat','🏷️','分类'],['kid','👦','孩子'],['set','⚙️','设置']];var body=state.tab==='dash'?renderDash():state.tab==='tpl'?renderTemplates():state.tab==='cat'?renderCats():state.tab==='kid'?renderKids():renderSet();app.innerHTML=body+''+tabs.map(function(t){return ''+t[1]+' '+t[2]+' '}).join('')+'
'}
async function go(tab){state.tab=tab;try{await loadAll();render()}catch(e){if(e.message.indexOf('密码')>=0){state.pw='';localStorage.removeItem('learn_pw')}render()}}
(async function(){if(state.pw){try{await loadAll()}catch(e){if(e.message.indexOf('密码')>=0){state.pw='';localStorage.removeItem('learn_pw')}}}render()})();`;
}
function wrapHtml(title, bodyExtra, script) {
return `
${title}
${title.includes('小助手') ? '
v1
' : '
'.replace('id="app"', '')}