Files
pipeline-lifetime/static/js/dashboard.js
T

152 lines
5.5 KiB
JavaScript

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 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;
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;
}
});