317 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			317 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| <!DOCTYPE html>
 | |
| <html lang="zh-CN">
 | |
| <head>
 | |
|     <meta charset="UTF-8">
 | |
|     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | |
|     <title>秀儿文件提交系统</title>
 | |
|     <style>
 | |
|         body {
 | |
|             font-family: 'Helvetica Neue', Arial, sans-serif;
 | |
|             background-color: #f0f2f5;
 | |
|             margin: 0;
 | |
|             padding: 0;
 | |
|             display: flex;
 | |
|             justify-content: center;
 | |
|             align-items: center;
 | |
|             min-height: 100vh;
 | |
|         }
 | |
|         .container {
 | |
|             background-color: #ffffff;
 | |
|             border-radius: 8px;
 | |
|             box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
 | |
|             padding: 30px;
 | |
|             width: 100%;
 | |
|             max-width: 400px;
 | |
|         }
 | |
|         .image-container {
 | |
|             margin-bottom: 20px;
 | |
|             border-radius: 8px;
 | |
|             overflow: hidden;
 | |
|         }
 | |
|         .image-container img {
 | |
|             width: 100%;
 | |
|             height: auto;
 | |
|             display: block;
 | |
|         }
 | |
|         h1 {
 | |
|             color: #333;
 | |
|             font-size: 24px;
 | |
|             margin-bottom: 20px;
 | |
|             text-align: center;
 | |
|         }
 | |
|         .form-field {
 | |
|             margin-bottom: 20px;
 | |
|         }
 | |
|         label {
 | |
|             display: block;
 | |
|             margin-bottom: 5px;
 | |
|             color: #555;
 | |
|             font-weight: 500;
 | |
|         }
 | |
|         input[type="text"] {
 | |
|             width: 100%;
 | |
|             padding: 10px;
 | |
|             border: 1px solid #ddd;
 | |
|             border-radius: 4px;
 | |
|             font-size: 16px;
 | |
|         }
 | |
|         .file-input-wrapper {
 | |
|             position: relative;
 | |
|             overflow: visible;
 | |
|             display: inline-block;
 | |
|             width: 100%;
 | |
|         }
 | |
|         .file-input-button {
 | |
|             background-color: #f0f0f0;
 | |
|             border: 1px solid #ddd;
 | |
|             color: #333;
 | |
|             padding: 10px 15px;
 | |
|             border-radius: 4px;
 | |
|             font-size: 16px;
 | |
|             cursor: pointer;
 | |
|             display: inline-block;
 | |
|             width: 100%;
 | |
|             text-align: center;
 | |
|             box-sizing: border-box;
 | |
|             transition: background-color 0.3s, border-color 0.3s;
 | |
|         }
 | |
|         .file-input-button:hover {
 | |
|             background-color: #e0e0e0;
 | |
|             border-color: #ccc;
 | |
|         }
 | |
|         .file-input-button input[type=file] {
 | |
|             font-size: 100px;
 | |
|             position: absolute;
 | |
|             left: 0;
 | |
|             top: 0;
 | |
|             opacity: 0;
 | |
|             cursor: pointer;
 | |
|             width: 100%;
 | |
|             height: 100%;
 | |
|         }
 | |
|         #file-name {
 | |
|             margin-top: 5px;
 | |
|             font-size: 14px;
 | |
|             color: #666;
 | |
|         }
 | |
|         button[type="submit"] {
 | |
|             background-color: #1890ff;
 | |
|             color: white;
 | |
|             border: none;
 | |
|             padding: 12px 20px;
 | |
|             border-radius: 4px;
 | |
|             font-size: 16px;
 | |
|             cursor: pointer;
 | |
|             width: 100%;
 | |
|             transition: background-color 0.3s;
 | |
|         }
 | |
|         button[type="submit"]:hover {
 | |
|             background-color: #40a9ff;
 | |
|         }
 | |
|         .progress-container {
 | |
|             margin-top: 20px;
 | |
|             display: none;
 | |
|         }
 | |
|         .progress {
 | |
|             height: 10px;
 | |
|             background-color: #f0f0f0;
 | |
|             border-radius: 5px;
 | |
|             overflow: hidden;
 | |
|         }
 | |
|         .progress-bar {
 | |
|             height: 100%;
 | |
|             background-color: #1890ff;
 | |
|             width: 0;
 | |
|             transition: width 0.3s ease;
 | |
|         }
 | |
|         #progress-percentage {
 | |
|             font-size: 14px;
 | |
|             color: #666;
 | |
|             margin-top: 5px;
 | |
|         }
 | |
|         #status {
 | |
|             margin-top: 15px;
 | |
|             font-size: 14px;
 | |
|             color: #666;
 | |
|         }
 | |
|         #download-link {
 | |
|             display: inline-block;
 | |
|             margin-top: 20px;
 | |
|             text-decoration: none;
 | |
|             color: #1890ff;
 | |
|             font-size: 14px;
 | |
|         }
 | |
|         .upload-stats {
 | |
|             margin-top: 10px;
 | |
|             font-size: 14px;
 | |
|             color: #666;
 | |
|         }
 | |
|         .upload-stats span {
 | |
|             display: inline-block;
 | |
|             margin-right: 15px;
 | |
|         }
 | |
|     </style>
 | |
| </head>
 | |
| <body>
 | |
|     <div class="container">
 | |
|         <div class="image-container">
 | |
|             <img src="image.jpg" alt="描述性文本">
 | |
|         </div>
 | |
|         <h1>秀儿文件提交系统</h1>
 | |
|         <form id="upload-form">
 | |
|             <div class="form-field">
 | |
|                 <label for="student">学生姓名:</label>
 | |
|                 <input type="text" id="student" name="student" required>
 | |
|             </div>
 | |
|             <div class="form-field">
 | |
|                 <label for="student-id">学号:</label>
 | |
|                 <input type="text" id="student-id" name="student-id" required>
 | |
|             </div>
 | |
|             <div class="form-field">
 | |
|                 <label for="file">选择文件:</label>
 | |
|                 <div class="file-input-wrapper">
 | |
|                     <div class="file-input-button">
 | |
|                         选择文件
 | |
|                         <input type="file" id="file" name="file" required>
 | |
|                     </div>
 | |
|                 </div>
 | |
|                 <div id="file-name">未选择文件</div>
 | |
|             </div>
 | |
|             <button type="submit">提交 (截止日期: 2024.07.03)</button>
 | |
|         </form>
 | |
|         <div id="status"></div>
 | |
|         <div class="progress-container" id="progress-container">
 | |
|             <div class="progress">
 | |
|                 <div class="progress-bar" id="progress-bar"></div>
 | |
|             </div>
 | |
|             <div id="progress-percentage">0%</div>
 | |
|             <div class="upload-stats">
 | |
|                 <span id="upload-speed">速度: 0 KB/s</span>
 | |
|                 <span id="upload-size">0 KB / 0 KB</span>
 | |
|                 <span id="upload-time">剩余时间: 计算中</span>
 | |
|             </div>
 | |
|         </div>
 | |
|         <a href="/download-submissions" id="download-link">下载统计表格</a>
 | |
|     </div>
 | |
| 
 | |
|     <script>
 | |
|         document.getElementById('upload-form').addEventListener('submit', async (e) => {
 | |
|     e.preventDefault();
 | |
| 
 | |
|     const student = document.getElementById('student').value;
 | |
|     const studentId = document.getElementById('student-id').value;
 | |
|     const file = document.getElementById('file').files[0];
 | |
|     const statusDiv = document.getElementById('status');
 | |
|     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');
 | |
| 
 | |
|     if (!student || !studentId || !file) {
 | |
|         alert('请提供学生姓名、学号,并选择一个文件。');
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     statusDiv.textContent = '准备上传...';
 | |
|     progressContainer.style.display = 'block';
 | |
| 
 | |
|     try {
 | |
|         const response = await fetch(`/generate-url?student=${encodeURIComponent(student)}&student_id=${encodeURIComponent(studentId)}&filename=${encodeURIComponent(file.name)}&content_type=${encodeURIComponent(file.type)}`, {
 | |
|             method: 'GET',
 | |
|         });
 | |
| 
 | |
|         if (!response.ok) {
 | |
|             const errorData = await response.json();
 | |
|             throw new Error(`生成预签名URL失败: ${errorData.error}`);
 | |
|         }
 | |
|         const data = await response.json();
 | |
| 
 | |
|         if (!data.url) {
 | |
|             throw new Error('服务器未返回URL');
 | |
|         }
 | |
| 
 | |
|         statusDiv.textContent = '正在上传文件...';
 | |
| 
 | |
|         const totalSize = file.size;
 | |
|         let uploadedSize = 0;
 | |
|         let startTime = Date.now();
 | |
| 
 | |
|         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); // 每次更新1%的进度
 | |
|             }
 | |
|         }, 200);
 | |
| 
 | |
|         // 实际上传
 | |
|         const uploadResponse = await fetch(data.url, {
 | |
|             method: 'PUT',
 | |
|             body: file,
 | |
|             headers: {
 | |
|                 'Content-Type': data.content_type
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         clearInterval(progressInterval);
 | |
| 
 | |
|         if (!uploadResponse.ok) {
 | |
|             throw new Error(`Upload failed: ${uploadResponse.statusText}`);
 | |
|         }
 | |
| 
 | |
|         // 确保进度达到100%
 | |
|         updateProgress(totalSize - uploadedSize);
 | |
| 
 | |
|         statusDiv.textContent = '文件上传成功';
 | |
|         alert('文件上传成功');
 | |
| 
 | |
|     } catch (error) {
 | |
|         console.error('错误:', error);
 | |
|         statusDiv.textContent = `错误: ${error.message}`;
 | |
|         alert('发生错误: ' + error.message);
 | |
|     }
 | |
| });
 | |
| 
 | |
| 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} 秒`;
 | |
| }
 | |
| 
 | |
| // 添加文件名显示功能
 | |
| document.getElementById('file').addEventListener('change', function(e) {
 | |
|     const fileName = e.target.files[0] ? e.target.files[0].name : '未选择文件';
 | |
|     document.getElementById('file-name').textContent = fileName;
 | |
| });
 | |
|     </script>
 | |
| </body>
 | |
| </html>
 | 
