// ── CONFIG (直接將 Worker 網址寫死,學生手機就不會迷路了) ── const API = 'https://misty-bonus-b5baclassroom-qa.lingochen.workers.dev'; let pollTimer = null; // ── API (加入時間戳記防快取,解決手機畫面不同步的問題) ── async function apiGet() { // 加上 ?t=時間戳記,強迫手機每次都抓取最新狀態,不使用舊暫存 const r = await fetch(API + '/state?t=' + Date.now()); if (!r.ok) throw new Error('fetch failed'); return r.json(); } async function apiPost(path, body) { const r = await fetch(API + path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (!r.ok) throw new Error('post failed'); return r.json(); } // ── NAV ── function showScreen(id) { document.querySelectorAll('.screen').forEach(s => s.classList.remove('active')); document.getElementById(id).classList.add('active'); } function goHome() { stopPolling(); showScreen('home'); } function goTeacher() { showScreen('teacher'); poll('teacher'); startPolling('teacher'); buildSharePanel(); } function goStudent() { studentSubmitted = false; showScreen('student'); poll('student'); startPolling('student'); } function startPolling(mode) { stopPolling(); pollTimer = setInterval(() => poll(mode), 2000); } function stopPolling() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } } async function poll(mode) { try { const state = await apiGet(); if (mode === 'teacher') renderTeacher(state); else renderStudent(state); } catch(e) { console.error(e); } } // ── TEACHER ── function renderTeacher(state) { const badge = document.getElementById('statusBadge'); if (state.status === 'open') badge.innerHTML = `作答開放中`; else if (state.status === 'closed') badge.innerHTML = `作答已關閉`; else badge.innerHTML = ''; document.getElementById('publishBtn').style.display = state.status === 'open' ? 'none' : ''; document.getElementById('closeBtn').style.display = state.status === 'open' ? '' : 'none'; document.getElementById('sharePanel').classList.toggle('hidden', state.status !== 'open'); const qi = document.getElementById('questionInput'); if (state.question && !qi.value) qi.value = state.question; const answers = state.answers || []; document.getElementById('countChip').textContent = answers.length + ' 份'; const grid = document.getElementById('answersGrid'); if (!answers.length) { grid.innerHTML = `
📭
發佈題目後,這裡會顯示學生的答案
`; return; } grid.innerHTML = answers.map(a => `
${a.name ? esc(a.name) : '匿名'}
${esc(a.text)}
`).join(''); } async function publishQuestion() { const q = document.getElementById('questionInput').value.trim(); if (!q) { alert('請先輸入題目!'); return; } try { await apiPost('/publish', { question: q }); } catch { alert('發佈失敗,請確認 Worker 網址正確'); } } async function closeQuestion() { try { await apiPost('/close', {}); } catch { alert('操作失敗'); } } async function clearAll() { if (!confirm('確定要清除題目與所有答案嗎?')) return; try { await apiPost('/clear', {}); document.getElementById('questionInput').value = ''; } catch { alert('操作失敗'); } } function buildSharePanel() { const url = window.location.href.split('?')[0] + '?role=student'; document.getElementById('shareUrl').textContent = url; const qrEl = document.getElementById('qrcode'); qrEl.innerHTML = ''; new QRCode(qrEl, { text: url, width: 72, height: 72, correctLevel: QRCode.CorrectLevel.L }); } // ── STUDENT ── let studentSubmitted = false; function renderStudent(state) { if (studentSubmitted) return; const w = document.getElementById('waitingView'); const a = document.getElementById('answerView'); const c = document.getElementById('closedView'); const s = document.getElementById('successView'); [w,a,c,s].forEach(v => v.style.display = 'none'); if (!state.question || state.status === 'idle') w.style.display = ''; else if (state.status === 'open') { document.getElementById('studentQuestion').textContent = state.question; a.style.display = ''; } else c.style.display = ''; } async function submitAnswer() { const name = document.getElementById('studentName').value.trim(); const text = document.getElementById('studentAnswer').value.trim(); if (!text) { alert('請輸入答案再提交!'); return; } const btn = document.getElementById('submitBtn'); btn.disabled = true; btn.textContent = '提交中…'; try { const res = await apiPost('/answer', { name, text }); if (res.error === 'closed') { alert('作答時間已結束!'); btn.disabled=false; btn.textContent='提交答案'; return; } studentSubmitted = true; stopPolling(); document.getElementById('answerView').style.display = 'none'; document.getElementById('successView').style.display = ''; } catch { alert('提交失敗,請重試!'); btn.disabled=false; btn.textContent='提交答案'; } } // ── PRESENTATION ── let slides = [], currentSlide = 0; async function startPresentation() { try { const state = await apiGet(); if (!state.question) { alert('請先發佈題目!'); return; } if (!(state.answers||[]).length){ alert('還沒有任何答案可以展示!'); return; } slides = [{ type:'title', question:state.question, count:state.answers.length }]; state.answers.forEach((a,i) => slides.push({ type:'answer', name:a.name, text:a.text, idx:i+1 })); currentSlide = 0; stopPolling(); showScreen('presentation'); renderSlide(); document.addEventListener('keydown', pptKey); } catch { alert('載入失敗,請重試'); } } function exitPresentation() { document.removeEventListener('keydown', pptKey); goTeacher(); } function pptKey(e) { if (e.key==='ArrowRight'||e.key===' '){e.preventDefault();pptNext();} if (e.key==='ArrowLeft'){e.preventDefault();pptPrev();} if (e.key==='Escape') exitPresentation(); } function renderSlide() { const s = slides[currentSlide], total = slides.length; const el = document.getElementById('pptContent'); if (s.type==='title') { el.innerHTML = `
本次題目
${esc(s.question)}
共 ${s.count} 份答案
`; } else { el.innerHTML = `
第 ${s.idx} / ${total-1} 份
${s.name?esc(s.name):'匿名'}
${esc(s.text)}
`; } el.style.animation='none'; el.offsetHeight; el.style.animation=''; document.getElementById('pptCounter').textContent = `${currentSlide+1} / ${total}`; document.getElementById('pptBar').style.width = ((currentSlide+1)/total*100)+'%'; document.getElementById('prevBtn').disabled = currentSlide===0; document.getElementById('nextBtn').disabled = currentSlide===total-1; } function pptNext(){if(currentSlide0){currentSlide--;renderSlide();}} function esc(s){return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"');} // ── INIT (自動跳轉邏輯) ── (function(){ // 如果網址帶有 role=student (也就是掃描 QR Code 進來的),直接進入學生畫面 if (location.search.includes('role=student')) { showScreen('student'); goStudent(); } else { showScreen('home'); } })();