Files
selfgroup/shortcut_trainer/static/common.js
2026-10-28 09:36:55 +08:00

29 lines
5.0 KiB
JavaScript

const API={async get(url){const r=await fetch(url,{cache:'no-store'});return r.json()},async post(url,data={}){const r=await fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});return r.json()},notify(el,msg){if(!el)return;el.textContent=msg;clearTimeout(el._t);el._t=setTimeout(()=>el.textContent='',3500)}};
function shortcutFromEvent(e){const p=[];if(e.ctrlKey)p.push('Ctrl');if(e.altKey)p.push('Alt');if(e.shiftKey)p.push('Shift');if(e.metaKey)p.push('Meta');let k=e.key.toUpperCase();if(k===' ')k='Space';if(['CONTROL','ALT','SHIFT','META'].includes(k))return '';p.push(k);return p.join('+')}
function keyName(e){const code=e.code||'';if(/^Key[A-Z]$/.test(code))return code.slice(3);if(/^Digit[0-9]$/.test(code))return code.slice(5);if(/^Numpad[0-9]$/.test(code))return code.slice(6);if(code==='Space')return 'SPACE';if(code==='ArrowLeft')return 'ARROWLEFT';if(code==='ArrowRight')return 'ARROWRIGHT';if(code==='Escape')return 'ESCAPE';let k=(e.key||'').toUpperCase();if(k===' ')return 'SPACE';if(k==='ESC')return 'ESCAPE';return k}
function consumeKey(e){e.preventDefault();e.stopPropagation()}
const RIBBON_PANELS=[
{key:'K',title:'检测',items:[{key:'C',name:'燃料检查',index:2},{key:'D',name:'系统诊断',index:4}]},
{key:'H',title:'推进',items:[{key:'R',name:'火箭准备',index:1},{key:'T',name:'通讯开启',index:3}]},
{key:'B',title:'保障',items:[{key:'P',name:'备用电源',index:5},{key:'L',name:'外部链接',index:6}]}
];
function ribbonPath(index){for(const panel of RIBBON_PANELS){for(const item of panel.items){if(item.index===Number(index)||item.name===index)return `Alt+${panel.key},然后 ${item.key}`}}return ''}
const LINK_TARGETS=[{key:'D',name:'百度',url:'https://www.baidu.com',aliases:['A','1']},{key:'F',name:'Bilibili',url:'https://www.bilibili.com',aliases:['B','2']}];
function linkByKey(k){return LINK_TARGETS.find(item=>item.key===k||(item.aliases||[]).includes(k))}
function studentNoOf(student){return (student&&(student.studentNo||student.student_no))||''}
function studentLabel(student){return student?`${student.name||''} · ${studentNoOf(student)}`:''}
function studentMustChange(student){return !!(student&&(student.mustChangePassword||student.must_change_password))}
let lastRoundId=undefined;
let shownResult=null;
let shownRoundId=null;
function formatClock(ts){const d=new Date(Number(ts||0)*1000);if(!ts||Number.isNaN(d.getTime()))return '';return d.toLocaleString('zh-CN',{hour12:false})}
function renderTaskResult(){const box=document.getElementById('taskResult');if(!box)return;const current=shownResult&&shownRoundId===lastRoundId?shownResult:null;if(!current){box.classList.add('hidden');box.style.display='none';box.innerHTML='';return}const rank=Number(current.rank);const total=Number(current.total);box.classList.remove('hidden');box.style.display='flex';box.innerHTML=`<strong>操作成功</strong><span>完成时间:${formatClock(current.finishedAt)}</span><span class="rank">当前排名:第 ${Number.isFinite(rank)&&rank>0?rank:1} 名 / 共 ${Number.isFinite(total)&&total>0?total:1} 人</span>`}
function keepTaskResult(result,roundId){if(!result)return;shownResult=result;shownRoundId=roundId??lastRoundId;renderTaskResult()}
function clearTaskResult(){shownResult=null;shownRoundId=null;renderTaskResult()}
async function applyStudentState(s){if(!s||!s.authenticated)return s;showRound(s.round);const roundId=s.round?s.round.id:null;const roundChanged=lastRoundId!==undefined&&lastRoundId!==roundId;lastRoundId=roundId;const me=document.getElementById('userChip')||document.getElementById('who');if(me&&s.student)me.textContent=studentLabel(s.student);if(roundChanged){clearTaskResult();if(roundId){flash('老师发布了新任务','success');if(typeof no==='number'&&no>=1&&no<=6)await reportEvent({type:'page',page:no})}else flash('老师已结束本轮任务')}if(s.result)keepTaskResult(s.result,roundId);else renderTaskResult();return s}
async function reportEvent(data){try{const r=await API.post('/api/event',data);if(r.correct)keepTaskResult(r.result||{rank:1,total:1,finishedAt:Math.floor(Date.now()/1000)},r.roundId??lastRoundId);return r}catch(e){return {ok:false}}}
function flash(msg,type='warn'){let el=document.getElementById('globalFlash');if(!el){el=document.createElement('div');el.id='globalFlash';el.style.cssText='position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:999;padding:14px 22px;border-radius:12px;font-weight:800;color:#fff';document.body.appendChild(el)}el.textContent=msg;el.style.background=type==='success'?'#16a34a':'#d97706';clearTimeout(el._t);el._t=setTimeout(()=>el.remove(),2200)}
function showRound(round){const text=document.getElementById('commandText'),state=document.getElementById('roundState');if(text)text.textContent=round?round.label:'等待老师开始比赛…';if(state)state.textContent=round?`比赛进行中 · #${round.id}`:'未开始'}
async function refreshStudent(){const s=await API.get('/api/student/state');if(!s.authenticated)return false;if(studentMustChange(s.student))flash('请先回控制台修改密码');await applyStudentState(s);return true}