js/main.js · 约 10345 字符
1(function(){
2 var KEY_R='temp_records_r13', KEY_S='temp_settings_r13', KEY_SEED='temp_seeded_r13';
3 // 默认阈值(口腔):正常 36.0~37.2;低热>=37.3;高热>=38.1;<36.0 偏低;<35.5 过低
4 var DEF={low:36.0, high:37.2, feverLo:37.3, feverHi:38.1, hypo:35.5};
5 function loadRec(){ try{return JSON.parse(localStorage.getItem(KEY_R))||{};}catch(e){return {};} }
6 function saveRec(o){ localStorage.setItem(KEY_R, JSON.stringify(o)); }
7 function loadSet(){ try{return Object.assign({},DEF,JSON.parse(localStorage.getItem(KEY_S))||{});}catch(e){return Object.assign({},DEF);} }
8 function saveSet(o){ localStorage.setItem(KEY_S, JSON.stringify(o)); }
9 function pad(n){return n<10?'0'+n:''+n;}
10 function iso(y,m,d){return y+'-'+pad(m+1)+'-'+pad(d);}
11 function todayISO(){var t=new Date();return iso(t.getFullYear(),t.getMonth(),t.getDate());}
12
13 // 体温分级
14 function grade(t){
15 var s=loadSet();
16 if(t< s.hypo) return {cls:'s-lo', label:'体温过低',ab:1,color:'#3b82f6'};
17 if(t< s.low) return {cls:'s-lo', label:'偏低', ab:1,color:'#3b82f6'};
18 if(t<=s.high) return {cls:'s-normal', label:'正常', ab:0,color:'#16a34a'};
19 if(t< s.feverHi)return {cls:'s-feverlo',label:'低热', ab:1,color:'#f97316'};
20 return {cls:'s-feverhi',label:'高热', ab:1,color:'#ef4444'};
21 }
22
23 var R=loadRec();
24 var view=new Date(); view.setDate(1);
25 var cur=todayISO();
26 var sel=cur;
27
28 function seedDemo(){
29 if(localStorage.getItem(KEY_SEED)) return;
30 var t=new Date(), y=t.getFullYear(), m=t.getMonth();
31 function add(d,tmp,site,time,note){
32 var k=iso(y,m,d);
33 if(!R[k]) R[k]=[];
34 R[k].push({t:tmp,site:site,time:time,note:note,ts:Date.now()});
35 }
36 add(2,36.5,'口腔','早','精神好');
37 add(4,37.8,'口腔','晚','有点乏力'); // 低热
38 add(6,38.6,'口腔','晚','头痛、畏寒'); // 高热
39 add(9,36.2,'腋下','早','');
40 add(11,38.2,'口腔','午','喉咙痛'); // 高热
41 add(14,35.2,'额温','早','手脚发凉'); // 体温过低
42 add(16,36.8,'口腔','早','正常');
43 add(18,37.5,'口腔','晚','轻微出汗'); // 低热
44 add(21,36.4,'口腔','早','');
45 add(24,39.0,'口腔','晚','持续高热'); // 高热
46 add(27,36.6,'腋下','早','');
47 saveRec(R); localStorage.setItem(KEY_SEED,'1');
48 }
49
50 function toast(msg){
51 var el=document.createElement('div'); el.className='toast'; el.textContent=msg;
52 document.body.appendChild(el);
53 requestAnimationFrame(function(){el.classList.add('show');});
54 setTimeout(function(){el.classList.remove('show'); setTimeout(function(){el.remove();},300);},1700);
55 }
56
57 function renderStats(){
58 var y=view.getFullYear(), m=view.getMonth();
59 var days=0, abn=0, hi=0, lo=999;
60 Object.keys(R).forEach(function(k){
61 if(k.indexOf(y+'-'+pad(m+1))!==0) return;
62 var arr=R[k]; if(!arr.length) return;
63 days++;
64 arr.forEach(function(r){ if(grade(r.t).ab) abn++; if(r.t>hi) hi=r.t; if(r.t<lo) lo=r.t; });
65 });
66 document.getElementById('stDays').textContent=days;
67 document.getElementById('stAbn').textContent=abn;
68 document.getElementById('stHi').textContent= hi>0? hi.toFixed(1)+'°':'—';
69 document.getElementById('stLo').textContent= lo<999? lo.toFixed(1)+'°':'—';
70 document.querySelector('.stats .warn').style.visibility = abn>0?'visible':'hidden';
71 }
72
73 function renderCalendar(){
74 var y=view.getFullYear(), m=view.getMonth();
75 var first=new Date(y,m,1).getDay(); // 0=日
76 var dim=new Date(y,m+1,0).getDate();
77 var pdim=new Date(y,m,0).getDate();
78 document.getElementById('calTitle').textContent=y+'年 '+(m+1)+'月';
79 var g=document.getElementById('grid'); g.innerHTML='';
80 // 上月补位
81 for(var i=0;i<first;i++){
82 var d=document.createElement('div'); d.className='cell out';
83 d.innerHTML='<span class="d">'+(pdim-first+1+i)+'</span>'; g.appendChild(d);
84 }
85 for(var dd=1;dd<=dim;dd++){
86 var k=iso(y,m,dd); var cell=document.createElement('div'); cell.className='cell';
87 var arr=R[k]||[];
88 var isT=(k===cur), isS=(k===sel);
89 if(isT) cell.className+=' today'; if(isS) cell.className+=' sel';
90 var html='<span class="d">'+dd+'</span>';
91 if(arr.length){
92 // 取当日“最异常”的一条作为代表
93 var worst=arr[0]; arr.forEach(function(r){ if(grade(r.t).ab && !grade(worst.t).ab) worst=r; else if(grade(r.t).ab===grade(worst.t).ab && Math.abs(r.t-37)>Math.abs(worst.t-37)) worst=r; });
94 var g0=grade(worst.t);
95 cell.className+=' has '+g0.cls;
96 html+='<span class="v">'+worst.t.toFixed(1)+'°</span>';
97 }
98 cell.innerHTML=html;
99 (function(kk){ cell.addEventListener('click',function(){ sel=kk; renderAll(); }); })(k);
100 g.appendChild(cell);
101 }
102 }
103
104 function renderDay(){
105 var box=document.getElementById('dayBox');
106 var arr=R[sel]||[];
107 var dt=new Date(sel.replace(/-/g,'/'));
108 var wk=['日','一','二','三','四','五','六'][dt.getDay()];
109 document.getElementById('dayDate').textContent=(sel===cur?'今天 · ':'')+sel+' 周'+wk;
110 if(!arr.length){
111 box.innerHTML='<div class="empty">这一天还没有体温记录<br>点击下方按钮添加一条记录 🌡️</div>';
112 return;
113 }
114 arr.sort(function(a,b){return (b.ts||0)-(a.ts||0);});
115 var h='';
116 arr.forEach(function(r,idx){
117 var g0=grade(r.t);
118 h+='<div class="rec"><div class="badge" style="background:'+g0.color+'"><b>'+r.t.toFixed(1)+'</b><i>'+g0.label+'</i></div>'
119 + '<div class="info"><div class="top">'+r.site+' · '+r.time+'</div>'
120 + '<div class="meta">'+(g0.ab?'⚠ 异常体温':'✓ 体温正常')+'</div>'
121 + (r.note?'<div class="note">备注:'+esc(r.note)+'</div>':'')
122 + '</div><div class="ops"><button class="del" data-i="'+idx+'">删除</button></div></div>';
123 });
124 box.innerHTML=h;
125 box.querySelectorAll('.del').forEach(function(b){
126 b.addEventListener('click',function(){
127 var i=+b.getAttribute('data-i');
128 R[sel].splice(i,1); if(!R[sel].length) delete R[sel];
129 saveRec(R); renderAll(); toast('已删除该记录');
130 });
131 });
132 }
133
134 function esc(s){return String(s).replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c];});}
135
136 function renderAll(){ renderStats(); renderCalendar(); renderDay(); }
137
138 // ---- 弹层:添加记录 ----
139 var sheet=document.getElementById('sheet'), mask=document.getElementById('mask');
140 var formSite='口腔', formTime='早', editing=sel;
141 function openSheet(){
142 editing=sel;
143 document.getElementById('sfDate').textContent=sel;
144 document.getElementById('sfTemp').value='';
145 document.getElementById('sfNote').value='';
146 setSeg('site',formSite); setSeg('time',formTime);
147 mask.classList.add('show'); sheet.classList.add('show');
148 }
149 function closeSheet(){ mask.classList.remove('show'); sheet.classList.remove('show'); }
150 function setSeg(name,val){
151 document.querySelectorAll('[data-seg="'+name+'"]').forEach(function(b){
152 b.classList.toggle('on', b.getAttribute('data-v')===val);
153 });
154 }
155 document.querySelectorAll('[data-seg="site"]').forEach(function(b){
156 b.addEventListener('click',function(){ formSite=b.getAttribute('data-v'); setSeg('site',formSite); });
157 });
158 document.querySelectorAll('[data-seg="time"]').forEach(function(b){
159 b.addEventListener('click',function(){ formTime=b.getAttribute('data-v'); setSeg('time',formTime); });
160 });
161 document.getElementById('mask').addEventListener('click',closeSheet);
162 document.getElementById('dayAdd').addEventListener('click',openSheet);
163 document.getElementById('sfCancel').addEventListener('click',closeSheet);
164 document.getElementById('sfSave').addEventListener('click',function(){
165 var v=parseFloat(document.getElementById('sfTemp').value);
166 if(isNaN(v)||v<30||v>45){ toast('请输入 30~45 之间的体温'); return; }
167 if(!R[sel]) R[sel]=[];
168 R[sel].push({t:v,site:formSite,time:formTime,note:document.getElementById('sfNote').value.trim(),ts:Date.now()});
169 saveRec(R); closeSheet(); renderAll();
170 toast(grade(v).ab?('已记录 · '+grade(v).label+' ⚠'):'已记录 · 体温正常 ✓');
171 });
172
173 // ---- 弹层:设置 ----
174 var setSheet=document.getElementById('setSheet'), setMask=document.getElementById('setMask');
175 function openSet(){
176 var s=loadSet();
177 document.getElementById('svLow').value=s.low;
178 document.getElementById('svHigh').value=s.high;
179 document.getElementById('svFeverLo').value=s.feverLo;
180 document.getElementById('svFeverHi').value=s.feverHi;
181 document.getElementById('svHypo').value=s.hypo;
182 setMask.classList.add('show'); setSheet.classList.add('show');
183 }
184 function closeSet(){ setMask.classList.remove('show'); setSheet.classList.remove('show'); }
185 document.getElementById('btnSet').addEventListener('click',openSet);
186 document.getElementById('setMask').addEventListener('click',closeSet);
187 document.getElementById('setCancel').addEventListener('click',closeSet);
188 document.getElementById('setSave').addEventListener('click',function(){
189 var g=function(id){var x=parseFloat(document.getElementById(id).value);return isNaN(x)?null:x;};
190 var s={low:g('svLow'),high:g('svHigh'),feverLo:g('svFeverLo'),feverHi:g('svFeverHi'),hypo:g('svHypo')};
191 saveSet(s); closeSet(); renderAll(); toast('阈值已更新,日历已重算');
192 });
193 document.getElementById('setReset').addEventListener('click',function(){
194 localStorage.removeItem(KEY_S); closeSet(); renderAll(); toast('已恢复默认阈值');
195 });
196
197 // ---- 月份切换 ----
198 document.getElementById('prevM').addEventListener('click',function(){ view.setMonth(view.getMonth()-1); renderAll(); });
199 document.getElementById('nextM').addEventListener('click',function(){ view.setMonth(view.getMonth()+1); renderAll(); });
200 document.getElementById('toToday').addEventListener('click',function(){ view=new Date(); view.setDate(1); sel=cur; renderAll(); });
201
202 // init
203 seedDemo(); R=loadRec(); renderAll();
204})();
205