const form = document.getElementById('predictForm'); const fileInput = document.getElementById('fileInput'); const dropZone = document.getElementById('dropZone'); const selectedFileName = document.getElementById('selectedFileName'); const submitBtn = document.getElementById('submitBtn'); const submitText = document.getElementById('submitText'); let submitIcon = document.getElementById('submitIcon'); const alertBox = document.getElementById('alertBox'); const alertIconWrap = document.getElementById('alertIconWrap'); const alertIcon = document.getElementById('alertIcon'); const alertTitle = document.getElementById('alertTitle'); const alertMessage = document.getElementById('alertMessage'); const alertClose = document.getElementById('alertClose'); const inlineResult = document.getElementById('inlineResult'); const resultPlaceholder = document.getElementById('resultPlaceholder'); const resultContent = document.getElementById('resultContent'); const resultImage = document.getElementById('resultImage'); const resultImportanceWrap = document.getElementById('resultImportanceWrap'); const resultImportanceImage = document.getElementById('resultImportanceImage'); const excelBtn = document.getElementById('excelBtn'); const resultPageBtn = document.getElementById('resultPageBtn'); let selectedFile = null; let alertTimer = null; let alertHideTimer = null; function hideAlert() { clearTimeout(alertTimer); clearTimeout(alertHideTimer); alertBox.classList.remove('opacity-100', 'translate-y-0'); alertBox.classList.add('pointer-events-none', 'opacity-0', 'translate-y-3'); alertHideTimer = setTimeout(() => { alertBox.classList.add('hidden'); }, 200); } function showAlert(message, type='error', title) { clearTimeout(alertTimer); clearTimeout(alertHideTimer); alertBox.classList.remove('hidden', 'border-red-200', 'border-blue-200'); alertIconWrap.classList.remove('bg-dangerSoft', 'text-dangerText', 'bg-blueSoft', 'text-primary'); if (type === 'error') { alertBox.classList.add('border-red-200'); alertIconWrap.classList.add('bg-dangerSoft', 'text-dangerText'); alertIcon.textContent = 'priority_high'; alertTitle.textContent = title || '需要补充文件'; } else { alertBox.classList.add('border-blue-200'); alertIconWrap.classList.add('bg-blueSoft', 'text-primary'); alertIcon.textContent = 'check_circle'; alertTitle.textContent = title || '处理完成'; } alertMessage.textContent = message; requestAnimationFrame(() => { alertBox.classList.remove('pointer-events-none', 'opacity-0', 'translate-y-3'); alertBox.classList.add('opacity-100', 'translate-y-0'); }); alertTimer = setTimeout(() => { hideAlert(); }, 10000); } alertClose.addEventListener('click', hideAlert); function updateSelectedFile(file) { selectedFile = file; selectedFileName.textContent = '已选择文件:' + file.name; selectedFileName.classList.remove('hidden'); } function renderResult(data) { resultImage.src = data.image_url; if (data.importance_url) { resultImportanceImage.src = data.importance_url; resultImportanceWrap.classList.remove('hidden'); } else { resultImportanceWrap.classList.add('hidden'); resultImportanceImage.removeAttribute('src'); } excelBtn.href = data.excel_url; resultPageBtn.href = data.result_url; resultPlaceholder.classList.add('hidden'); resultContent.classList.remove('hidden'); resultContent.classList.add('flex'); } fileInput.addEventListener('change', () => { const file = fileInput.files[0]; if (!file) return; updateSelectedFile(file); }); ['dragenter', 'dragover'].forEach(evt => dropZone.addEventListener(evt, e => { e.preventDefault(); e.stopPropagation(); e.dataTransfer.dropEffect = 'copy'; dropZone.classList.add('border-primary', 'bg-blue-50'); })); dropZone.addEventListener('dragleave', e => { e.preventDefault(); e.stopPropagation(); dropZone.classList.remove('border-primary', 'bg-blue-50'); }); dropZone.addEventListener('drop', e => { e.preventDefault(); e.stopPropagation(); dropZone.classList.remove('border-primary', 'bg-blue-50'); const file = e.dataTransfer.files[0]; if (!file) return; if (window.DataTransfer) { const transfer = new DataTransfer(); transfer.items.add(file); fileInput.files = transfer.files; } updateSelectedFile(file); }); form.addEventListener('submit', async (e) => { e.preventDefault(); hideAlert(); if (!selectedFile) { showAlert('点击上传区域选择电子表格或逗号分隔值文件,也可以直接把文件拖放到上传框内。', 'error', '请先选择文件'); dropZone.classList.add('border-red-300', 'bg-red-50', 'ring-4', 'ring-red-100'); dropZone.scrollIntoView({ behavior: 'smooth', block: 'center' }); setTimeout(() => { dropZone.classList.remove('border-red-300', 'bg-red-50', 'ring-4', 'ring-red-100'); }, 2400); return; } submitBtn.disabled = true; submitText.textContent = '预测中...'; submitIcon.replaceWith(Object.assign(document.createElement('span'), { id: 'submitSpinner', className: 'spinner' })); try { const formData = new FormData(form); formData.set('file', selectedFile); const resp = await fetch(form.action, { method: 'POST', body: formData }); const data = await resp.json(); if (!resp.ok) { showAlert(data.error || '预测失败,请稍后重试。'); return; } renderResult(data); showAlert('预测完成,已生成图表与电子表格报告。', 'success'); inlineResult.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } catch (err) { showAlert('请求失败,请检查后端服务是否正常。'); } finally { submitBtn.disabled = false; submitText.textContent = '分析并预测'; const restoredIcon = Object.assign(document.createElement('span'), { id: 'submitIcon', className: 'material-symbols-outlined', textContent: 'analytics' }); document.getElementById('submitSpinner')?.replaceWith(restoredIcon); submitIcon = restoredIcon; } });