276 lines
11 KiB
JavaScript
276 lines
11 KiB
JavaScript
document.querySelector('.file-input-button').addEventListener('click', function() {
|
|
document.getElementById('file').click();
|
|
});
|
|
|
|
document.getElementById('file').addEventListener('change', function(e) {
|
|
const fileName = e.target.files[0] ? e.target.files[0].name : '未选择文件';
|
|
document.getElementById('file-name').textContent = fileName;
|
|
});
|
|
|
|
document.getElementById('upload-form').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
handleSubmission();
|
|
});
|
|
|
|
document.getElementById('verify-submit-button').addEventListener('click', async () => {
|
|
await validateCodeAndSubmit();
|
|
});
|
|
|
|
async function handleSubmission() {
|
|
const assignment = document.getElementById('assignment').value;
|
|
const file = document.getElementById('file').files[0];
|
|
const statusDiv = document.getElementById('status');
|
|
|
|
if (!assignment || !file) {
|
|
alert('请选择作业,并选择一个文件。');
|
|
return;
|
|
}
|
|
|
|
setLoadingState(true);
|
|
|
|
try {
|
|
const recordResponse = await fetch('/record-submission', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ assignment, filename: file.name })
|
|
});
|
|
|
|
if (recordResponse.status === 401) {
|
|
setLoadingState(false);
|
|
document.getElementById('verification-container').style.display = 'block';
|
|
|
|
const emailResponse = await fetch('/generate-code', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ assignment })
|
|
});
|
|
|
|
if (emailResponse.ok) {
|
|
alert('作业已提交过,请输入验证码提交');
|
|
} else {
|
|
const errorData = await emailResponse.json();
|
|
throw new Error(`发送验证码失败: ${errorData.error}`);
|
|
}
|
|
} else if (!recordResponse.ok) {
|
|
const errorRecordData = await recordResponse.json();
|
|
throw new Error(`记录提交信息失败: ${errorRecordData.error}`);
|
|
} else {
|
|
const data = await recordResponse.json();
|
|
await uploadFile(data.upload_url, file, statusDiv);
|
|
alert('文件上传并记录成功');
|
|
}
|
|
} catch (error) {
|
|
console.error('错误:', error);
|
|
statusDiv.textContent = `错误: ${error.message}`;
|
|
alert('发生错误: ' + error.message);
|
|
} finally {
|
|
setLoadingState(false);
|
|
}
|
|
}
|
|
|
|
async function validateCodeAndSubmit() {
|
|
const assignment = document.getElementById('assignment').value;
|
|
const file = document.getElementById('file').files[0];
|
|
const code = document.getElementById('verification-code').value;
|
|
const statusDiv = document.getElementById('status');
|
|
|
|
setLoadingState(true, true);
|
|
|
|
try {
|
|
const validateResponse = await fetch('/validate-code', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ assignment, code })
|
|
});
|
|
|
|
if (validateResponse.ok) {
|
|
const { upload_url } = await validateResponse.json();
|
|
await uploadFile(upload_url, file, statusDiv);
|
|
alert('文件上传并记录成功');
|
|
document.getElementById('verification-container').style.display = 'none';
|
|
} else {
|
|
const errorData = await validateResponse.json();
|
|
throw new Error(`验证码错误: ${errorData.error}`);
|
|
}
|
|
} catch (error) {
|
|
console.error('错误:', error);
|
|
statusDiv.textContent = `错误: ${error.message}`;
|
|
alert('发生错误: ' + error.message);
|
|
} finally {
|
|
setLoadingState(false, true);
|
|
}
|
|
}
|
|
|
|
async function uploadFile(upload_url, file, statusDiv) {
|
|
const progressContainer = document.getElementById('progress-container');
|
|
const progressBar = document.getElementById('progress-bar');
|
|
const progressPercentage = document.getElementById('progress-percentage');
|
|
const uploadSpeed = document.getElementById('upload-speed');
|
|
const uploadSize = document.getElementById('upload-size');
|
|
const uploadTime = document.getElementById('upload-time');
|
|
|
|
statusDiv.textContent = '正在上传文件...';
|
|
const totalSize = file.size;
|
|
let uploadedSize = 0;
|
|
let startTime = Date.now();
|
|
|
|
progressContainer.style.display = 'block';
|
|
|
|
const updateProgress = (additionalProgress = 0) => {
|
|
uploadedSize += additionalProgress;
|
|
const percentComplete = (uploadedSize / totalSize) * 100;
|
|
const elapsedTime = (Date.now() - startTime) / 1000;
|
|
const speed = uploadedSize / elapsedTime;
|
|
const remainingSize = totalSize - uploadedSize;
|
|
const estimatedRemainingTime = speed > 0 ? remainingSize / speed : 0;
|
|
|
|
progressBar.style.width = percentComplete + '%';
|
|
progressPercentage.textContent = percentComplete.toFixed(2) + '%';
|
|
uploadSpeed.textContent = `速度: ${formatSize(speed)}/s`;
|
|
uploadSize.textContent = `${formatSize(uploadedSize)} / ${formatSize(totalSize)}`;
|
|
uploadTime.textContent = `剩余时间: ${formatTime(estimatedRemainingTime)}`;
|
|
};
|
|
|
|
const progressInterval = setInterval(() => {
|
|
if (uploadedSize < totalSize) {
|
|
updateProgress(totalSize / 100);
|
|
}
|
|
}, 200);
|
|
|
|
const uploadResponse = await fetch(upload_url, {
|
|
method: 'PUT',
|
|
body: file,
|
|
headers: {
|
|
'Content-Type': 'application/octet-stream'
|
|
}
|
|
});
|
|
|
|
clearInterval(progressInterval);
|
|
|
|
if (!uploadResponse.ok) {
|
|
throw new Error(`上传失败: ${uploadResponse.statusText}`);
|
|
}
|
|
|
|
updateProgress(totalSize - uploadedSize);
|
|
}
|
|
|
|
function setLoadingState(isLoading, isVerify = false) {
|
|
const submitButton = document.querySelector('button[type="submit"]');
|
|
const verifyButton = document.getElementById('verify-submit-button');
|
|
const container = isVerify ? verifyButton.parentElement : submitButton.parentElement;
|
|
|
|
const loadingIndicator = container.querySelector('.loading-indicator') || document.createElement('div');
|
|
loadingIndicator.className = 'loading-indicator';
|
|
loadingIndicator.innerHTML = '<span>加载中...</span>';
|
|
loadingIndicator.style.display = isLoading ? 'block' : 'none';
|
|
|
|
if (!loadingIndicator.parentElement) {
|
|
container.appendChild(loadingIndicator);
|
|
}
|
|
|
|
if (isVerify) {
|
|
verifyButton.disabled = isLoading;
|
|
} else {
|
|
submitButton.disabled = isLoading;
|
|
}
|
|
}
|
|
|
|
function formatSize(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
|
|
function formatTime(seconds) {
|
|
if (isNaN(seconds) || !isFinite(seconds)) {
|
|
return '计算中';
|
|
}
|
|
if (seconds < 60) return Math.round(seconds) + ' 秒';
|
|
const minutes = Math.floor(seconds / 60);
|
|
const remainingSeconds = Math.round(seconds % 60);
|
|
return `${minutes} 分 ${remainingSeconds} 秒`;
|
|
}
|
|
|
|
function previewTable(apiUrl, title) {
|
|
fetch(apiUrl)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const newWindow = window.open('', '_blank');
|
|
newWindow.document.write('<html><head><title>' + title + '</title><style>');
|
|
newWindow.document.write('table { width: 100%; border-collapse: collapse; margin-top: 10px; }');
|
|
newWindow.document.write('th, td { border: 1px solid #ddd; padding: 8px; text-align: left; word-wrap: break-word; }');
|
|
newWindow.document.write('th { background-color: #f2f2f2; }');
|
|
newWindow.document.write('body { font-family: Arial, sans-serif; padding: 20px; }');
|
|
newWindow.document.write('</style></head><body>');
|
|
newWindow.document.write('<h2>' + title + '</h2>');
|
|
|
|
const table = newWindow.document.createElement('table');
|
|
|
|
if (data.length > 0) {
|
|
let headers;
|
|
if (title === '预览统计表格') {
|
|
headers = ['时间', '提交的文件', '姓名', '学号', '作业'];
|
|
} else {
|
|
headers = ['学号', '姓名', '作业', '提交情况', '提交的文件'];
|
|
}
|
|
|
|
const thead = newWindow.document.createElement('thead');
|
|
const tr = newWindow.document.createElement('tr');
|
|
headers.forEach((header, index) => {
|
|
const th = newWindow.document.createElement('th');
|
|
th.textContent = header;
|
|
// 设置列宽
|
|
if (title === '预览统计表格') {
|
|
if (index === 0) th.style.width = '20%'; // 时间
|
|
else if (index === 1) th.style.width = '30%'; // 提交的文件
|
|
else if (index === 2) th.style.width = '15%'; // 姓名
|
|
else if (index === 3) th.style.width = '15%'; // 学号
|
|
else if (index === 4) th.style.width = '20%'; // 作业
|
|
} else {
|
|
if (index === 0) th.style.width = '15%'; // 学号
|
|
else if (index === 1) th.style.width = '15%'; // 姓名
|
|
else if (index === 2) th.style.width = '20%'; // 作业
|
|
else if (index === 3) th.style.width = '15%'; // 提交情况
|
|
else if (index === 4) th.style.width = '35%'; // 提交的文件
|
|
}
|
|
tr.appendChild(th);
|
|
});
|
|
thead.appendChild(tr);
|
|
table.appendChild(thead);
|
|
}
|
|
|
|
const tbody = newWindow.document.createElement('tbody');
|
|
data.forEach(row => {
|
|
const tr = newWindow.document.createElement('tr');
|
|
if (title === '预览统计表格') {
|
|
['时间', '提交的文件', '姓名', '学号', '作业'].forEach(key => {
|
|
const td = newWindow.document.createElement('td');
|
|
td.textContent = row[key] || '';
|
|
tr.appendChild(td);
|
|
});
|
|
} else {
|
|
['学号', '姓名', '作业', '提交情况', '提交的文件'].forEach(key => {
|
|
const td = newWindow.document.createElement('td');
|
|
td.textContent = row[key] || '';
|
|
tr.appendChild(td);
|
|
});
|
|
}
|
|
tbody.appendChild(tr);
|
|
});
|
|
|
|
table.appendChild(tbody);
|
|
newWindow.document.body.appendChild(table);
|
|
newWindow.document.write('</body></html>');
|
|
newWindow.document.close();
|
|
})
|
|
.catch(error => console.error('Error fetching data:', error));
|
|
}
|