Spaces:
Sleeping
Sleeping
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>智能口算大师 - Math Master</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.2/axios.min.js"></script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap'); | |
| body { font-family: 'Noto Sans SC', sans-serif; } | |
| .paper-bg { | |
| background-color: #ffffff; | |
| background-image: radial-gradient(#e5e7eb 1px, transparent 1px); | |
| background-size: 20px 20px; | |
| } | |
| /* Custom scrollbar */ | |
| ::-webkit-scrollbar { width: 6px; } | |
| ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 min-h-screen text-gray-800 font-sans"> | |
| <div class="container mx-auto px-4 py-6 flex flex-col md:flex-row gap-6 h-screen overflow-hidden"> | |
| <!-- Sidebar: Settings --> | |
| <div class="w-full md:w-1/3 lg:w-1/4 bg-white rounded-2xl shadow-xl flex flex-col h-full border border-indigo-50"> | |
| <div class="p-6 pb-2"> | |
| <h1 class="text-2xl font-bold text-indigo-600 flex items-center"> | |
| <span class="bg-indigo-100 p-2 rounded-lg mr-3"> | |
| <svg class="w-6 h-6 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg> | |
| </span> | |
| 口算大师 | |
| </h1> | |
| <p class="text-xs text-gray-400 mt-1 ml-1">专业的小学数学出题神器</p> | |
| </div> | |
| <div class="flex-1 overflow-y-auto px-6 py-2 space-y-6"> | |
| <!-- Operations --> | |
| <div class="bg-gray-50 p-4 rounded-xl border border-gray-100"> | |
| <label class="block text-sm font-bold text-gray-700 mb-3 flex items-center"> | |
| <svg class="w-4 h-4 mr-1 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.384-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path></svg> | |
| 运算类型 | |
| </label> | |
| <div class="grid grid-cols-2 gap-3"> | |
| <label class="flex items-center space-x-2 bg-white border p-2 rounded-lg cursor-pointer hover:border-indigo-300 transition shadow-sm"> | |
| <input type="checkbox" class="op-check form-checkbox text-indigo-600 rounded w-5 h-5" value="+" checked> | |
| <span class="font-medium text-gray-700">+ 加法</span> | |
| </label> | |
| <label class="flex items-center space-x-2 bg-white border p-2 rounded-lg cursor-pointer hover:border-indigo-300 transition shadow-sm"> | |
| <input type="checkbox" class="op-check form-checkbox text-indigo-600 rounded w-5 h-5" value="-" checked> | |
| <span class="font-medium text-gray-700">- 减法</span> | |
| </label> | |
| <label class="flex items-center space-x-2 bg-white border p-2 rounded-lg cursor-pointer hover:border-indigo-300 transition shadow-sm"> | |
| <input type="checkbox" class="op-check form-checkbox text-indigo-600 rounded w-5 h-5" value="×"> | |
| <span class="font-medium text-gray-700">× 乘法</span> | |
| </label> | |
| <label class="flex items-center space-x-2 bg-white border p-2 rounded-lg cursor-pointer hover:border-indigo-300 transition shadow-sm"> | |
| <input type="checkbox" class="op-check form-checkbox text-indigo-600 rounded w-5 h-5" value="÷"> | |
| <span class="font-medium text-gray-700">÷ 除法</span> | |
| </label> | |
| </div> | |
| </div> | |
| <!-- Range & Count --> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-sm font-bold text-gray-700 mb-2">数字范围 (1-100)</label> | |
| <div class="flex items-center space-x-2"> | |
| <input type="number" id="minVal" value="1" class="w-full bg-gray-50 border-gray-200 rounded-lg focus:ring-indigo-500 focus:border-indigo-500 p-2.5 border text-center font-mono" placeholder="Min"> | |
| <span class="text-gray-400 font-bold">-</span> | |
| <input type="number" id="maxVal" value="20" class="w-full bg-gray-50 border-gray-200 rounded-lg focus:ring-indigo-500 focus:border-indigo-500 p-2.5 border text-center font-mono" placeholder="Max"> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="flex justify-between items-center mb-2"> | |
| <label class="text-sm font-bold text-gray-700">题目数量</label> | |
| <span id="countDisplay" class="bg-indigo-100 text-indigo-700 px-2 py-0.5 rounded text-sm font-bold">50</span> | |
| </div> | |
| <input type="range" id="countRange" min="10" max="100" value="50" step="10" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-indigo-600"> | |
| </div> | |
| </div> | |
| <!-- Advanced Options --> | |
| <div class="bg-indigo-50/50 p-4 rounded-xl border border-indigo-100 space-y-3"> | |
| <h3 class="text-xs font-bold text-indigo-400 uppercase tracking-wider">高级选项</h3> | |
| <label class="flex items-center space-x-3 cursor-pointer group"> | |
| <div class="relative flex items-center"> | |
| <input type="checkbox" id="enableBlank" class="peer h-5 w-5 cursor-pointer appearance-none rounded-md border border-gray-300 bg-white checked:border-indigo-600 checked:bg-indigo-600 focus:outline-none transition-all"> | |
| <svg class="pointer-events-none absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-3.5 h-3.5 text-white opacity-0 peer-checked:opacity-100" viewBox="0 0 14 14" fill="none"><path d="M3 8L6 11L11 3.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> | |
| </div> | |
| <span class="text-sm font-medium text-gray-700 group-hover:text-indigo-700 transition">填空题模式 (如: 3 + ? = 5)</span> | |
| </label> | |
| <label class="flex items-center space-x-3 cursor-pointer group"> | |
| <div class="relative flex items-center"> | |
| <input type="checkbox" id="enableAnswers" class="peer h-5 w-5 cursor-pointer appearance-none rounded-md border border-gray-300 bg-white checked:border-indigo-600 checked:bg-indigo-600 focus:outline-none transition-all"> | |
| <svg class="pointer-events-none absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-3.5 h-3.5 text-white opacity-0 peer-checked:opacity-100" viewBox="0 0 14 14" fill="none"><path d="M3 8L6 11L11 3.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> | |
| </div> | |
| <span class="text-sm font-medium text-gray-700 group-hover:text-indigo-700 transition">附带答案页 (PDF)</span> | |
| </label> | |
| <div class="flex items-center justify-between text-sm"> | |
| <span class="font-medium text-gray-700">PDF 列数:</span> | |
| <div class="flex bg-white rounded-lg p-1 border shadow-sm"> | |
| <label class="px-3 py-1 rounded cursor-pointer transition" id="col3_label"> | |
| <input type="radio" name="cols" value="3" class="hidden" onchange="updateColUI(this)"> | |
| 3列 | |
| </label> | |
| <label class="px-3 py-1 rounded bg-indigo-100 text-indigo-700 font-bold cursor-pointer transition" id="col4_label"> | |
| <input type="radio" name="cols" value="4" class="hidden" onchange="updateColUI(this)" checked> | |
| 4列 | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Actions --> | |
| <div class="p-6 pt-2 space-y-3 bg-white border-t border-gray-100"> | |
| <button onclick="generatePreview()" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3.5 px-4 rounded-xl shadow-lg shadow-indigo-200 transform transition hover:-translate-y-0.5 active:translate-y-0 flex justify-center items-center"> | |
| <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path></svg> | |
| 生成题目预览 | |
| </button> | |
| <button onclick="downloadPDF()" class="w-full bg-white border-2 border-indigo-600 text-indigo-600 hover:bg-indigo-50 font-bold py-3.5 px-4 rounded-xl shadow-sm transform transition hover:-translate-y-0.5 active:translate-y-0 flex justify-center items-center group"> | |
| <svg class="w-5 h-5 mr-2 group-hover:animate-bounce" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg> | |
| 下载 PDF 试卷 | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Main: Preview --> | |
| <div class="flex-1 bg-white rounded-2xl shadow-xl overflow-hidden flex flex-col border border-gray-100"> | |
| <div class="bg-gray-50 px-6 py-4 border-b flex justify-between items-center"> | |
| <div class="flex items-center space-x-2"> | |
| <div class="h-3 w-3 rounded-full bg-red-400"></div> | |
| <div class="h-3 w-3 rounded-full bg-yellow-400"></div> | |
| <div class="h-3 w-3 rounded-full bg-green-400"></div> | |
| </div> | |
| <h2 class="font-bold text-gray-700">A4 试卷预览</h2> | |
| <div class="text-xs text-gray-400"> | |
| 仅供预览,请以 PDF 为准 | |
| </div> | |
| </div> | |
| <div class="flex-1 overflow-y-auto p-8 paper-bg relative" id="paperContainer"> | |
| <div id="previewArea" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-y-8 gap-x-4 text-xl font-mono text-gray-800"> | |
| <!-- Problems go here --> | |
| <div class="text-center text-gray-400 col-span-full py-32 flex flex-col items-center justify-center"> | |
| <svg class="w-16 h-16 text-gray-200 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg> | |
| <p>请点击左侧“生成题目预览”</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // UI Helpers | |
| const countRange = document.getElementById('countRange'); | |
| const countDisplay = document.getElementById('countDisplay'); | |
| countRange.addEventListener('input', (e) => countDisplay.innerText = e.target.value); | |
| function updateColUI(radio) { | |
| document.querySelectorAll('input[name="cols"]').forEach(r => { | |
| const label = r.parentElement; | |
| if (r.checked) { | |
| label.classList.add('bg-indigo-100', 'text-indigo-700', 'font-bold'); | |
| } else { | |
| label.classList.remove('bg-indigo-100', 'text-indigo-700', 'font-bold'); | |
| } | |
| }); | |
| // Update preview grid if generated | |
| const grid = document.getElementById('previewArea'); | |
| if (radio.value == '3') { | |
| grid.classList.remove('lg:grid-cols-4'); | |
| grid.classList.add('lg:grid-cols-3'); | |
| } else { | |
| grid.classList.remove('lg:grid-cols-3'); | |
| grid.classList.add('lg:grid-cols-4'); | |
| } | |
| } | |
| function getSettings() { | |
| const ops = Array.from(document.querySelectorAll('.op-check:checked')).map(cb => cb.value); | |
| const min = parseInt(document.getElementById('minVal').value) || 1; | |
| const max = parseInt(document.getElementById('maxVal').value) || 20; | |
| const count = parseInt(countRange.value) || 50; | |
| const blank_prob = document.getElementById('enableBlank').checked ? 0.5 : 0; | |
| const with_answers = document.getElementById('enableAnswers').checked; | |
| const cols = document.querySelector('input[name="cols"]:checked').value; | |
| if (ops.length === 0) { | |
| alert('请至少选择一种运算类型!'); | |
| return null; | |
| } | |
| return { ops, min, max, count, blank_prob, with_answers, cols }; | |
| } | |
| async function generatePreview() { | |
| const settings = getSettings(); | |
| if (!settings) return; | |
| const previewArea = document.getElementById('previewArea'); | |
| // Apply grid cols immediately for visual feedback | |
| if (settings.cols == '3') { | |
| previewArea.classList.remove('lg:grid-cols-4'); | |
| previewArea.classList.add('lg:grid-cols-3'); | |
| } else { | |
| previewArea.classList.remove('lg:grid-cols-3'); | |
| previewArea.classList.add('lg:grid-cols-4'); | |
| } | |
| previewArea.innerHTML = '<div class="col-span-full text-center py-32"><div class="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 mx-auto"></div></div>'; | |
| try { | |
| const res = await axios.post('/api/preview', settings); | |
| const problems = res.data; | |
| previewArea.innerHTML = ''; | |
| problems.forEach((p, index) => { | |
| const el = document.createElement('div'); | |
| // Simulating paper look | |
| el.className = 'py-2 px-2 flex items-center hover:bg-indigo-50/50 rounded transition select-text'; | |
| el.innerHTML = ` | |
| <span class="font-bold text-gray-400 mr-3 text-sm w-6 text-right">${index + 1}.</span> | |
| <span class="text-2xl font-mono text-gray-800 tracking-wide">${p.question.replace(/\s/g, ' ')}</span> | |
| `; | |
| previewArea.appendChild(el); | |
| }); | |
| } catch (err) { | |
| console.error(err); | |
| previewArea.innerHTML = '<div class="col-span-full text-center text-red-500 py-20">生成失败,请重试</div>'; | |
| } | |
| } | |
| async function downloadPDF() { | |
| const settings = getSettings(); | |
| if (!settings) return; | |
| const btn = event.currentTarget; | |
| const originalText = btn.innerHTML; | |
| btn.innerHTML = '<svg class="animate-spin h-5 w-5 mr-2 text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>生成中...'; | |
| btn.disabled = true; | |
| try { | |
| const response = await fetch('/api/download', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(settings) | |
| }); | |
| if (!response.ok) throw new Error('Download failed'); | |
| const blob = await response.blob(); | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `Math_Worksheet_${new Date().getTime()}.pdf`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| a.remove(); | |
| window.URL.revokeObjectURL(url); | |
| } catch (err) { | |
| alert('下载失败: ' + err.message); | |
| } finally { | |
| btn.innerHTML = originalText; | |
| btn.disabled = false; | |
| } | |
| } | |
| // Initial load | |
| generatePreview(); | |
| </script> | |
| </body> | |
| </html> | |