Spaces:
Running
Running
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>学生成绩查询系统</title> | |
| <!-- 引入Google字体和图标 --> | |
| <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;600;700&display=swap" rel="stylesheet"> | |
| <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> | |
| <!-- 引入Chart.js库 --> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <style> | |
| :root { | |
| --primary-color: #4361ee; | |
| --primary-hover: #3a51db; | |
| --secondary-color: #6c757d; | |
| --success-color: #28a745; | |
| --danger-color: #dc3545; | |
| --warning-color: #ffc107; | |
| --info-color: #0dcaf0; | |
| --light-color: #f8f9fa; | |
| --dark-color: #343a40; | |
| --white: #ffffff; | |
| --body-bg: #f0f2f5; | |
| --card-bg: var(--white); | |
| --border-radius: 12px; | |
| --box-shadow: 0 6px 20px rgba(0, 0, 0, 0.07); | |
| --transition: all 0.3s ease; | |
| /* 新增对比颜色 - 与compare页面一致 */ | |
| --student-a-color: #4361ee; | |
| --student-a-light: rgba(67, 97, 238, 0.2); | |
| --student-b-color: #ff6b6b; | |
| --student-b-light: rgba(255, 107, 107, 0.2); | |
| } | |
| /* 趋势图标样式 */ | |
| .meta-value-wrapper { | |
| display: flex; | |
| align-items: center; | |
| gap: 4px; | |
| } | |
| .trend-icon { | |
| font-size: 18px; | |
| font-weight: bold; | |
| } | |
| .trend-icon.up, .trend-text.up { | |
| color: var(--success-color); | |
| } | |
| .trend-icon.down, .trend-text.down { | |
| color: var(--danger-color); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Nunito', Arial, sans-serif; | |
| background-color: var(--body-bg); | |
| color: #333; | |
| line-height: 1.6; | |
| padding: 0; | |
| margin: 0; | |
| } | |
| .container { | |
| max-width: 1100px; | |
| margin: 20px auto; | |
| padding: 0 15px; | |
| } | |
| .header { | |
| text-align: center; | |
| padding: 30px 0 20px; | |
| } | |
| .header h1 { | |
| color: var(--primary-color); | |
| font-size: 2.4rem; | |
| margin-bottom: 10px; | |
| font-weight: 700; | |
| text-shadow: 1px 1px 2px rgba(0,0,0,0.1); | |
| } | |
| .header p { | |
| color: var(--secondary-color); | |
| font-size: 1.1rem; | |
| } | |
| .card { | |
| background-color: var(--card-bg); | |
| border-radius: var(--border-radius); | |
| box-shadow: var(--box-shadow); | |
| padding: 25px; | |
| margin-bottom: 30px; | |
| transition: var(--transition); | |
| animation: fadeIn 0.3s ease; | |
| } | |
| .card:hover { | |
| box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); | |
| } | |
| .search-form { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 20px; | |
| } | |
| .search-section { | |
| display: flex; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| gap: 30px; | |
| margin-bottom: 20px; | |
| position: relative; | |
| } | |
| .search-container { | |
| position: relative; | |
| margin: 0 auto; | |
| max-width: 500px; | |
| width: 100%; | |
| } | |
| .search-input { | |
| width: 100%; | |
| padding: 16px 20px; | |
| padding-left: 50px; | |
| border: 2px solid #e1e5ea; | |
| border-radius: 30px; | |
| font-size: 16px; | |
| transition: var(--transition); | |
| outline: none; | |
| background-color: var(--white); | |
| } | |
| .search-input:focus { | |
| border-color: var(--primary-color); | |
| box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.2); | |
| } | |
| .search-icon { | |
| position: absolute; | |
| left: 16px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| color: #aab0b7; | |
| } | |
| .search-btn { | |
| background-color: var(--primary-color); | |
| color: white; | |
| border: none; | |
| border-radius: 30px; | |
| padding: 14px 30px; | |
| font-size: 16px; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| font-weight: 600; | |
| display: block; | |
| margin: 0 auto; | |
| min-width: 150px; | |
| } | |
| .search-btn:hover { | |
| background-color: var(--primary-hover); | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(67, 97, 238, 0.3); | |
| } | |
| .search-btn:active { | |
| transform: translateY(0); | |
| box-shadow: none; | |
| } | |
| /* 搜索模式切换 */ | |
| .search-toggle { | |
| display: flex; | |
| background-color: #edf2ff; | |
| border-radius: 50px; | |
| padding: 3px; | |
| margin-bottom: 20px; | |
| width: 400px; | |
| max-width: 100%; | |
| margin: 0 auto 20px auto; | |
| } | |
| .search-toggle-btn { | |
| flex: 1; | |
| border: none; | |
| padding: 10px 15px; | |
| font-size: 14px; | |
| font-weight: 600; | |
| background: transparent; | |
| color: var(--secondary-color); | |
| cursor: pointer; | |
| border-radius: 50px; | |
| transition: all 0.2s ease; | |
| } | |
| .search-toggle-btn.active { | |
| background-color: var(--primary-color); | |
| color: white; | |
| box-shadow: 0 3px 8px rgba(67, 97, 238, 0.25); | |
| } | |
| /* 姓名搜索区域 */ | |
| .name-search-section, .idcard-search-section { | |
| display: none; | |
| width: 100%; | |
| max-width: 500px; | |
| margin: 0 auto; | |
| } | |
| .name-search-section.active, .idcard-search-section.active { | |
| display: block; | |
| } | |
| .name-search-input { | |
| width: 100%; | |
| padding: 16px 20px; | |
| padding-left: 50px; | |
| border: 2px solid #e1e5ea; | |
| border-radius: 30px; | |
| font-size: 16px; | |
| transition: var(--transition); | |
| outline: none; | |
| background-color: var(--white); | |
| } | |
| .name-search-input:focus { | |
| border-color: var(--primary-color); | |
| box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.2); | |
| } | |
| .student-list { | |
| background-color: white; | |
| border-radius: 12px; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.1); | |
| margin-top: 10px; | |
| max-height: 300px; | |
| overflow-y: auto; | |
| overflow-x: hidden; | |
| display: none; | |
| position: relative; | |
| z-index: 1000; | |
| /* 移动设备滚动优化 */ | |
| -webkit-overflow-scrolling: touch; | |
| touch-action: pan-y; | |
| overscroll-behavior-y: contain; | |
| scrollbar-width: thin; | |
| scrollbar-color: #c1c1c1 #f1f1f1; | |
| } | |
| .student-list::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| .student-list::-webkit-scrollbar-track { | |
| background: #f1f1f1; | |
| border-radius: 3px; | |
| } | |
| .student-list::-webkit-scrollbar-thumb { | |
| background: #c1c1c1; | |
| border-radius: 3px; | |
| } | |
| .student-list::-webkit-scrollbar-thumb:hover { | |
| background: #a8a8a8; | |
| } | |
| .student-item { | |
| padding: 15px; | |
| border-bottom: 1px solid #f0f0f0; | |
| cursor: pointer; | |
| transition: background-color 0.2s; | |
| -webkit-tap-highlight-color: transparent; | |
| } | |
| .student-item:hover { | |
| background-color: #f8faff; | |
| } | |
| .student-item:last-child { | |
| border-bottom: none; | |
| } | |
| .student-name { | |
| font-weight: 600; | |
| color: #333; | |
| margin-bottom: 5px; | |
| pointer-events: none; | |
| } | |
| .student-id { | |
| color: #777; | |
| font-size: 14px; | |
| pointer-events: none; | |
| } | |
| #loading-message { | |
| display: none; | |
| text-align: center; | |
| padding: 30px; | |
| } | |
| .loader { | |
| width: 48px; | |
| height: 48px; | |
| border: 5px solid var(--light-color); | |
| border-bottom-color: var(--primary-color); | |
| border-radius: 50%; | |
| display: inline-block; | |
| box-sizing: border-box; | |
| animation: rotation 1s linear infinite; | |
| margin: 0 auto; | |
| } | |
| @keyframes rotation { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .loading-text { | |
| margin-top: 15px; | |
| color: var(--primary-color); | |
| font-weight: 600; | |
| } | |
| .error-message { | |
| color: var(--danger-color); | |
| background-color: rgba(220, 53, 69, 0.1); | |
| border-left: 4px solid var(--danger-color); | |
| padding: 15px; | |
| border-radius: 5px; | |
| margin: 20px 0; | |
| font-weight: 600; | |
| } | |
| .success-message { | |
| color: var(--success-color); | |
| background-color: rgba(40, 167, 69, 0.1); | |
| border-left: 4px solid var(--success-color); | |
| padding: 15px; | |
| border-radius: 5px; | |
| margin: 20px 0; | |
| font-weight: 600; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .info-message { | |
| color: var(--secondary-color); | |
| background-color: rgba(108, 117, 125, 0.1); | |
| border-left: 4px solid var(--secondary-color); | |
| padding: 15px; | |
| border-radius: 5px; | |
| margin: 20px 0; | |
| } | |
| .tabs { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 2px; | |
| margin-top: 20px; | |
| border-radius: 8px 8px 0 0; | |
| overflow: hidden; | |
| } | |
| .tab-button { | |
| background-color: #f0f2f8; | |
| border: none; | |
| padding: 14px 20px; | |
| cursor: pointer; | |
| flex-grow: 1; | |
| text-align: center; | |
| transition: var(--transition); | |
| font-weight: 600; | |
| border-bottom: 3px solid transparent; | |
| color: var(--secondary-color); | |
| } | |
| .tab-button:hover { | |
| background-color: #e9ecf1; | |
| } | |
| .tab-button.active { | |
| background-color: var(--white); | |
| color: var(--primary-color); | |
| border-bottom: 3px solid var(--primary-color); | |
| } | |
| .tab-content { | |
| display: none; | |
| padding: 25px; | |
| background-color: var(--white); | |
| border-radius: 0 0 12px 12px; | |
| animation: fadeIn 0.5s ease-in-out; | |
| } | |
| .tab-content.active { | |
| display: block; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| .score-header { | |
| border-bottom: 1px solid #eaecef; | |
| padding-bottom: 15px; | |
| margin-bottom: 25px; | |
| } | |
| .score-header h3 { | |
| color: var(--primary-color); | |
| font-size: 1.6rem; | |
| margin-bottom: 5px; | |
| } | |
| .score-meta { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); | |
| gap: 20px; | |
| margin-bottom: 25px; | |
| background-color: #f8faff; | |
| padding: 20px; | |
| border-radius: 12px; | |
| } | |
| .meta-item { | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .meta-label { | |
| font-size: 0.9rem; | |
| color: var(--secondary-color); | |
| margin-bottom: 5px; | |
| } | |
| .meta-value { | |
| font-size: 1.2rem; | |
| font-weight: 600; | |
| color: #333; | |
| } | |
| .score-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin: 20px 0; | |
| border-radius: 8px; | |
| overflow: hidden; | |
| box-shadow: 0 0 10px rgba(0,0,0,0.03); | |
| } | |
| .score-table th, | |
| .score-table td { | |
| border: 1px solid #eaecef; | |
| padding: 14px 15px; | |
| text-align: left; | |
| } | |
| .score-table th { | |
| background-color: #f6f8fa; | |
| font-weight: 600; | |
| color: #444; | |
| position: sticky; | |
| top: 0; | |
| z-index: 1; | |
| } | |
| .score-table tr:nth-child(even) { | |
| background-color: #fafbff; | |
| } | |
| .score-table tr:hover { | |
| background-color: rgba(67, 97, 238, 0.05); | |
| } | |
| .summary-box { | |
| background-color: #f8fafd; | |
| border-radius: 12px; | |
| padding: 20px; | |
| margin-top: 25px; | |
| border: 1px solid #eaecef; | |
| } | |
| .summary-title { | |
| font-weight: 600; | |
| color: #455a64; | |
| margin-bottom: 15px; | |
| font-size: 1.1rem; | |
| } | |
| .summary-content { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); | |
| gap: 20px; | |
| } | |
| .summary-item { | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .tips { | |
| list-style-type: none; | |
| padding-left: 0; | |
| } | |
| .tips li { | |
| padding: 7px 0; | |
| position: relative; | |
| padding-left: 25px; | |
| } | |
| .tips li:before { | |
| content: "•"; | |
| position: absolute; | |
| left: 10px; | |
| color: var(--primary-color); | |
| font-weight: bold; | |
| } | |
| .material-icons { | |
| vertical-align: middle; | |
| font-size: 20px; | |
| } | |
| /* 趋势图相关样式 */ | |
| .trend-section { | |
| margin-top: 30px; | |
| } | |
| .single-exam-analysis { | |
| margin-top: 30px; | |
| padding-top: 20px; | |
| border-top: 1px solid #e1e5ea; | |
| } | |
| .single-exam-analysis h4 { | |
| color: var(--dark-color); | |
| margin-bottom: 20px; | |
| text-align: center; | |
| font-size: 1.1rem; | |
| } | |
| .chart-card { | |
| background-color: var(--white); | |
| border-radius: var(--border-radius); | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.05); | |
| padding: 20px; | |
| min-height: 300px; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .chart-area { | |
| flex: 1; | |
| position: relative; | |
| min-height: 250px; | |
| width: 100%; | |
| overflow: hidden; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .no-chart-data { | |
| color: var(--secondary-color); | |
| text-align: center; | |
| font-style: italic; | |
| padding: 40px 20px; | |
| } | |
| .trend-section { | |
| margin-top: 35px; | |
| padding-top: 25px; | |
| border-top: 1px dashed #e0e4e7; | |
| } | |
| .chart-container { | |
| width: 100%; | |
| height: 400px; | |
| margin-top: 20px; | |
| position: relative; | |
| } | |
| /* 图表区域样式 - 与compare页面保持一致 */ | |
| .chart-area { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 100%; | |
| height: 100%; | |
| position: relative; | |
| min-height: 300px; | |
| } | |
| .chart-card { | |
| background: white; | |
| border-radius: var(--border-radius); | |
| box-shadow: var(--box-shadow); | |
| margin-bottom: 20px; | |
| padding: 20px; | |
| display: flex; | |
| flex-direction: column; | |
| height: 400px; | |
| } | |
| .chart-card h4 { | |
| margin: 0 0 20px 0; | |
| color: var(--dark-color); | |
| font-size: 1.1rem; | |
| text-align: center; | |
| } | |
| } | |
| .chart-tabs { | |
| display: flex; | |
| background-color: #f1f3f5; | |
| border-radius: 10px; | |
| margin-bottom: 20px; | |
| overflow: hidden; | |
| width: 400px; | |
| max-width: 100%; | |
| } | |
| .chart-tab { | |
| flex: 1; | |
| text-align: center; | |
| padding: 12px 15px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| border: none; | |
| background: transparent; | |
| color: #495057; | |
| } | |
| .chart-tab.active { | |
| background-color: var(--primary-color); | |
| color: white; | |
| box-shadow: 0 2px 10px rgba(67, 97, 238, 0.2); | |
| } | |
| .chart-view { | |
| display: none; | |
| } | |
| .chart-view.active { | |
| display: block; | |
| animation: fadeIn 0.5s ease; | |
| } | |
| .no-trend-data { | |
| text-align: center; | |
| padding: 30px 20px; | |
| color: var(--secondary-color); | |
| font-style: italic; | |
| background-color: #f8fafd; | |
| border-radius: 10px; | |
| border: 1px dashed #e0e4e7; | |
| } | |
| /* 统计区域样式更新为与compare.html一致 */ | |
| .statistics-section { | |
| margin-top: 35px; | |
| padding-top: 25px; | |
| border-top: 1px dashed #e0e4e7; | |
| animation: fadeIn 0.5s ease; | |
| } | |
| .statistics-section h4 { | |
| color: var(--primary-color); | |
| font-size: 1.3rem; | |
| margin-bottom: 20px; | |
| font-weight: 600; | |
| } | |
| .statistics-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin-top: 15px; | |
| border-radius: 8px; | |
| overflow: hidden; | |
| box-shadow: 0 0 10px rgba(0,0,0,0.05); | |
| } | |
| .statistics-table th, | |
| .statistics-table td { | |
| border: 1px solid #eaecef; | |
| padding: 12px 15px; | |
| text-align: left; | |
| font-size: 0.95rem; | |
| } | |
| .statistics-table th { | |
| background-color: #f6f8fa; | |
| font-weight: 600; | |
| color: #444; | |
| position: sticky; | |
| top: 0; | |
| z-index: 1; | |
| } | |
| .statistics-table tr:nth-child(even) { | |
| background-color: #fafbff; | |
| } | |
| .statistics-table tr:hover { | |
| background-color: rgba(67, 97, 238, 0.05); | |
| } | |
| .no-stats-data { | |
| text-align: center; | |
| padding: 30px 20px; | |
| color: var(--secondary-color); | |
| font-style: italic; | |
| background-color: #f8fafd; | |
| border-radius: 10px; | |
| border: 1px dashed #e0e4e7; | |
| margin-top: 15px; | |
| } | |
| /* 科目选择器 */ | |
| .subject-toggles { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 8px; | |
| margin: 15px 0; | |
| justify-content: center; | |
| } | |
| .subject-toggle { | |
| display: inline-flex; | |
| align-items: center; | |
| background-color: #f0f2f8; | |
| padding: 6px 12px; | |
| border-radius: 20px; | |
| font-size: 13px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| user-select: none; | |
| border: 1px solid transparent; | |
| } | |
| .subject-toggle.active { | |
| background-color: var(--primary-color); | |
| color: white; | |
| } | |
| .subject-toggle.active:before { | |
| content: "✓"; | |
| margin-right: 5px; | |
| font-weight: bold; | |
| } | |
| .color-indicator { | |
| width: 10px; | |
| height: 10px; | |
| border-radius: 50%; | |
| display: inline-block; | |
| margin-right: 8px; | |
| } | |
| /* 移动设备优化 */ | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 0 10px; | |
| margin: 10px auto; | |
| } | |
| .header h1 { | |
| font-size: 1.8rem; | |
| margin-bottom: 8px; | |
| } | |
| .header p { | |
| font-size: 1rem; | |
| } | |
| .card { | |
| padding: 20px 15px; | |
| margin-bottom: 20px; | |
| } | |
| .search-form { | |
| flex-direction: column; | |
| } | |
| .score-meta { | |
| grid-template-columns: 1fr; | |
| } | |
| .summary-content { | |
| grid-template-columns: 1fr; | |
| } | |
| .tabs { | |
| flex-direction: column; | |
| } | |
| .search-toggle { | |
| width: 100%; | |
| } | |
| .chart-container { | |
| height: 340px; | |
| } | |
| .student-list { | |
| max-height: 200px; | |
| -webkit-overflow-scrolling: touch; | |
| touch-action: pan-y; | |
| overscroll-behavior-y: contain; | |
| position: fixed; | |
| z-index: 9999; | |
| left: 10px; | |
| right: 10px; | |
| width: auto; | |
| } | |
| .chart-card { | |
| height: 300px; | |
| padding: 15px; | |
| } | |
| .chart-card h4 { | |
| font-size: 1rem; | |
| margin-bottom: 15px; | |
| } | |
| .chart-area { | |
| min-height: 200px; | |
| max-height: 220px; | |
| width: 100%; | |
| } | |
| .subject-toggles { | |
| gap: 6px; | |
| margin: 12px 0; | |
| } | |
| .subject-toggle { | |
| padding: 5px 10px; | |
| font-size: 12px; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| .container { | |
| padding: 0 8px; | |
| margin: 8px auto; | |
| } | |
| .header { | |
| padding: 20px 0 15px; | |
| } | |
| .header h1 { | |
| font-size: 1.5rem; | |
| } | |
| .header p { | |
| font-size: 0.9rem; | |
| } | |
| .card { | |
| padding: 15px 10px; | |
| margin-bottom: 15px; | |
| } | |
| .search-container { | |
| width: 100%; | |
| } | |
| .summary-content { | |
| grid-template-columns: 1fr; | |
| } | |
| .score-table { | |
| font-size: 0.9rem; | |
| } | |
| .chart-container { | |
| height: 280px; | |
| } | |
| .student-list { | |
| max-height: 150px; | |
| overflow-y: scroll; | |
| -webkit-overflow-scrolling: touch; | |
| touch-action: pan-y; | |
| overscroll-behavior-y: contain; | |
| position: fixed; | |
| z-index: 9999; | |
| left: 8px; | |
| right: 8px; | |
| width: auto; | |
| } | |
| .chart-card { | |
| height: 250px; | |
| padding: 12px; | |
| } | |
| .chart-card h4 { | |
| font-size: 0.9rem; | |
| margin-bottom: 12px; | |
| } | |
| .chart-area { | |
| min-height: 160px; | |
| max-height: 180px; | |
| } | |
| .subject-toggle { | |
| padding: 4px 8px; | |
| font-size: 11px; | |
| } | |
| .statistics-table { | |
| font-size: 12px; | |
| } | |
| .statistics-table th, | |
| .statistics-table td { | |
| padding: 8px 6px; | |
| } | |
| } | |
| /* 触摸设备滚动优化 */ | |
| @media (pointer: coarse) { | |
| .student-list { | |
| -webkit-overflow-scrolling: touch; | |
| scrollbar-width: thin; | |
| } | |
| .student-list::-webkit-scrollbar { | |
| width: 4px; | |
| } | |
| .student-list::-webkit-scrollbar-track { | |
| background: #f1f1f1; | |
| border-radius: 2px; | |
| } | |
| .student-list::-webkit-scrollbar-thumb { | |
| background: #c1c1c1; | |
| border-radius: 2px; | |
| } | |
| .student-list::-webkit-scrollbar-thumb:hover { | |
| background: #a8a8a8; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>学生成绩查询系统</h1> | |
| <p>输入姓名或身份证号查询考试成绩</p> | |
| <div style="margin-top: 15px;"> | |
| <a href="/compare" style="color: var(--primary-color); text-decoration: none; font-weight: 600; display: inline-flex; align-items: center; gap: 5px; padding: 8px 16px; border-radius: 20px; border: 2px solid var(--primary-color); transition: var(--transition);" | |
| onmouseover="this.style.backgroundColor='var(--primary-color)'; this.style.color='white';" | |
| onmouseout="this.style.backgroundColor='transparent'; this.style.color='var(--primary-color)';"> | |
| <span class="material-icons">compare_arrows</span>成绩比较 | |
| </a> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <!-- 搜索方式切换 --> | |
| <div class="search-toggle"> | |
| <button type="button" class="search-toggle-btn active" data-target="name-search">按姓名查询</button> | |
| <button type="button" class="search-toggle-btn" data-target="idcard-search">按身份证号查询</button> | |
| </div> | |
| <!-- 姓名搜索区域 --> | |
| <div class="name-search-section active"> | |
| <div class="search-container"> | |
| <input type="text" id="name-input" class="name-search-input" placeholder="请输入学生姓名"> | |
| <span class="material-icons search-icon">person_search</span> | |
| </div> | |
| <div id="student-list" class="student-list" style="display:none;"></div> | |
| </div> | |
| <!-- 身份证搜索区域 --> | |
| <div class="idcard-search-section"> | |
| <form action="/query" method="post" id="queryForm" class="search-form"> | |
| <div class="search-container"> | |
| <input type="text" name="id_number" id="id-number-input" class="search-input" placeholder="请输入18位身份证号" value="{{ result.id_number_submitted if result and result.id_number_submitted else '' }}" required> | |
| <span class="material-icons search-icon">pin</span> | |
| </div> | |
| <button type="submit" class="search-btn">查询成绩</button> | |
| </form> | |
| </div> | |
| </div> | |
| <div id="loading-message"> | |
| <span class="loader"></span> | |
| <p class="loading-text">正在查询成绩,请稍候...</p> | |
| </div> | |
| {% if result %} | |
| <div class="result"> | |
| {% if result.error_message %} | |
| <div class="card"> | |
| <div class="error-message"> | |
| <span class="material-icons">error</span> 查询失败: {{ result.error_message }} | |
| </div> | |
| {% if 'cookie' in result.error_message.lower() %} | |
| <div class="info-message"> | |
| <strong>提示:</strong> | |
| <ul class="tips"> | |
| <li>请确保服务器上的 cookies.txt 文件存在且有效</li> | |
| <li>如果问题持续,请联系管理员更新Cookie</li> | |
| </ul> | |
| </div> | |
| {% endif %} | |
| </div> | |
| {% elif result.scores_data %} | |
| <div class="card"> | |
| <div class="success-message"> | |
| <span class="material-icons">check_circle</span> 成功查询到 {{ result.scores_data|length }} 条成绩记录 | |
| </div> | |
| {% if result.scores_data|length > 1 %} | |
| <div class="tabs"> | |
| {% for record in result.scores_data %} | |
| <button class="tab-button {% if loop.first %}active{% endif %}" onclick="openTab(event, 'record-{{ loop.index }}')"> | |
| {{ record.get('考试名称', '考试') }} | |
| </button> | |
| {% endfor %} | |
| </div> | |
| {% endif %} | |
| {% for record in result.scores_data %} | |
| <div id="record-{{ loop.index }}" class="tab-content {% if loop.first %}active{% endif %}"> | |
| <div class="score-header"> | |
| <h3>{{ record.get('考试名称', '考试成绩') }}</h3> | |
| <div class="score-meta"> | |
| <div class="meta-item"> | |
| <span class="meta-label">考试时间</span> | |
| <span class="meta-value">{{ record.get('考试时间', '未知') }}</span> | |
| </div> | |
| <div class="meta-item"> | |
| <span class="meta-label">学生姓名</span> | |
| <span class="meta-value">{{ record.get('姓名', '未知') }}</span> | |
| </div> | |
| <div class="meta-item"> | |
| <span class="meta-label">学号</span> | |
| <span class="meta-value">{{ record.get('学号', '未知') }}</span> | |
| </div> | |
| <div class="meta-item"> | |
| <span class="meta-label">总分</span> | |
| <span class="meta-value">{{ record.get('总分', '未知') }}</span> | |
| </div> | |
| <div class="meta-item"> | |
| <span class="meta-label">大类排名</span> | |
| <span class="meta-value">{{ record.get('大类排名', '未知') }}</span> | |
| </div> | |
| <div class="meta-item"> | |
| <span class="meta-label">组合排名</span> | |
| <span class="meta-value">{{ record.get('组合排名', '未知') }}</span> | |
| </div> | |
| <div class="meta-item"> | |
| <span class="meta-label">排名变动</span> | |
| <div class="meta-value-wrapper"> | |
| {% set rank_trend = record.get('rank_trend', '无数据') %} | |
| {% if '↑' in rank_trend %} | |
| <span class="material-icons trend-icon up">arrow_upward</span> | |
| <span class="trend-text up">{{ rank_trend|replace('↑', '') }}</span> | |
| {% elif '↓' in rank_trend %} | |
| <span class="material-icons trend-icon down">arrow_downward</span> | |
| <span class="trend-text down">{{ rank_trend|replace('↓', '') }}</span> | |
| {% else %} | |
| <span class="trend-text">{{ rank_trend }}</span> | |
| {% endif %} | |
| </div> | |
| </div> | |
| <div class="meta-item"> | |
| <span class="meta-label">总分变动</span> | |
| <div class="meta-value-wrapper"> | |
| {% set score_trend = record.get('score_trend', '无数据') %} | |
| {% if '↑' in score_trend %} | |
| <span class="material-icons trend-icon up">arrow_upward</span> | |
| <span class="trend-text up">{{ score_trend|replace('↑', '') }}</span> | |
| {% elif '↓' in score_trend %} | |
| <span class="material-icons trend-icon down">arrow_downward</span> | |
| <span class="trend-text down">{{ score_trend|replace('↓', '') }}</span> | |
| {% else %} | |
| <span class="trend-text">{{ score_trend }}</span> | |
| {% endif %} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <table class="score-table"> | |
| <thead> | |
| <tr> | |
| <th>科目</th> | |
| <th>成绩</th> | |
| <th>校次</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {% for subject in ['语文', '数学', '英语', '物理', '化学', '生物', '历史', '政治', '地理'] %} | |
| {% if record.get(subject) and record.get(subject) != '无数据' %} | |
| <tr> | |
| <td>{{ subject }}</td> | |
| <td>{{ record.get(subject, '-') }}</td> | |
| <td>{{ record.get(subject ~ '校次', '-') }}</td> | |
| </tr> | |
| {% endif %} | |
| {% endfor %} | |
| </tbody> | |
| </table> | |
| <!-- 单次考试雷达图分析 --> | |
| <div class="single-exam-analysis"> | |
| <h4>科目能力雷达图</h4> | |
| <div class="chart-card"> | |
| <div class="chart-area"> | |
| <canvas id="radar-chart-{{ loop.index }}"></canvas> | |
| <div id="no-radar-data-{{ loop.index }}" class="no-chart-data" style="display:none;"> | |
| 暂无完整科目数据显示雷达图 | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {% endfor %} | |
| {% if result.scores_data|length > 1 %} | |
| <div class="fade-in" style="padding: 20px 0;"> | |
| <!-- 趋势图部分 --> | |
| <div class="trend-section"> | |
| <h4 style="text-align: center; font-size: 1.5rem; color: var(--primary-color); margin-bottom: 20px;">历史成绩趋势分析</h4> | |
| <!-- 科目选择器 --> | |
| <div class="subject-toggles" id="subject-toggles"> | |
| <!-- 科目选择器将由JavaScript动态生成 --> | |
| </div> | |
| <!-- 趋势图 --> | |
| <div class="chart-card"> | |
| <h4>排名趋势对比</h4> | |
| <div class="chart-area"> | |
| <canvas id="trend-chart-canvas"></canvas> | |
| <div id="no-trend-data" class="no-trend-data" style="display:none;"> | |
| 暂无足够数据显示趋势,至少需要两次考试记录。 | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 统计数据部分 --> | |
| <div class="statistics-section" id="statistics-section"> | |
| <!-- 统计内容将由JavaScript动态生成 --> | |
| </div> | |
| </div> | |
| {% endif %} | |
| </div> | |
| {% else %} | |
| {% if result.message %} | |
| <div class="card"> | |
| <div class="info-message"> | |
| <span class="material-icons">info</span> {{ result.message }} | |
| </div> | |
| <p class="info-message">请检查身份证号是否正确。</p> | |
| </div> | |
| {% endif %} | |
| {% endif %} | |
| </div> | |
| {% endif %} | |
| </div> | |
| <script> | |
| // 全局变量 | |
| let examDataCache = null; | |
| let charts = {}; // 用于存储图表实例 | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const form = document.getElementById('queryForm'); | |
| const loadingMessage = document.getElementById('loading-message'); | |
| const nameInput = document.getElementById('name-input'); | |
| const idNumberInput = document.getElementById('id-number-input'); | |
| const studentListDiv = document.getElementById('student-list'); | |
| // 搜索方式切换 | |
| const toggleBtns = document.querySelectorAll('.search-toggle-btn'); | |
| const nameSearchSection = document.querySelector('.name-search-section'); | |
| const idcardSearchSection = document.querySelector('.idcard-search-section'); | |
| toggleBtns.forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| // 取消所有按钮的激活状态 | |
| toggleBtns.forEach(b => b.classList.remove('active')); | |
| // 激活当前按钮 | |
| this.classList.add('active'); | |
| // 显示对应的搜索区域 | |
| const target = this.dataset.target; | |
| if (target === 'name-search') { | |
| if (nameSearchSection) nameSearchSection.classList.add('active'); | |
| if (idcardSearchSection) idcardSearchSection.classList.remove('active'); | |
| } else { | |
| if (nameSearchSection) nameSearchSection.classList.remove('active'); | |
| if (idcardSearchSection) idcardSearchSection.classList.add('active'); | |
| } | |
| }); | |
| }); | |
| // 姓名搜索功能 | |
| let typingTimer; | |
| const doneTypingInterval = 500; | |
| if (nameInput) { | |
| nameInput.addEventListener('input', function() { | |
| clearTimeout(typingTimer); | |
| if (this.value.trim()) { | |
| typingTimer = setTimeout(searchStudents, doneTypingInterval); | |
| } else { | |
| if (studentListDiv) { | |
| studentListDiv.style.display = 'none'; | |
| } | |
| } | |
| }); | |
| } | |
| // 姓名搜索函数 | |
| function searchStudents() { | |
| const name = nameInput ? nameInput.value.trim() : ''; | |
| if (!name || !studentListDiv) return; | |
| studentListDiv.innerHTML = '<div style="text-align:center;padding:15px;">正在搜索...</div>'; | |
| studentListDiv.style.display = 'block'; | |
| const formData = new FormData(); | |
| formData.append('name', name); | |
| fetch('/search_student', { | |
| method: 'POST', | |
| body: formData | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.success && data.students && data.students.length > 0) { | |
| studentListDiv.innerHTML = ''; | |
| data.students.forEach(student => { | |
| const div = document.createElement('div'); | |
| div.className = 'student-item fade-in'; | |
| div.innerHTML = ` | |
| <div class="student-name">${student.姓名}</div> | |
| <div class="student-id">${student.身份证号}</div> | |
| `; | |
| div.addEventListener('click', function(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| if (idNumberInput) { | |
| idNumberInput.value = student.身份证号; | |
| if (form) { | |
| form.submit(); | |
| } | |
| } | |
| studentListDiv.style.display = 'none'; | |
| if (loadingMessage) loadingMessage.style.display = 'block'; | |
| }); | |
| // 移动端触摸优化 | |
| div.addEventListener('touchstart', function(e) { | |
| e.stopPropagation(); | |
| }, { passive: true }); | |
| studentListDiv.appendChild(div); | |
| }); | |
| } else { | |
| studentListDiv.innerHTML = '<div style="text-align:center;padding:15px;color:#888;">未找到匹配的学生</div>'; | |
| } | |
| }) | |
| .catch(error => { | |
| console.error('搜索学生出错:', error); | |
| studentListDiv.innerHTML = '<div style="text-align:center;padding:15px;color:red;">搜索出错,请稍后再试</div>'; | |
| }); | |
| } | |
| // 点击页面其他地方关闭学生列表 | |
| document.addEventListener('click', function(event) { | |
| if (nameInput && studentListDiv && | |
| !nameInput.contains(event.target) && | |
| !studentListDiv.contains(event.target)) { | |
| studentListDiv.style.display = 'none'; | |
| } | |
| }); | |
| // 阻止学生列表的滚动事件冒泡 | |
| if (studentListDiv) { | |
| studentListDiv.addEventListener('touchmove', function(e) { | |
| e.stopPropagation(); | |
| }, { passive: true }); | |
| } | |
| // 表单提交处理 | |
| if (form && loadingMessage) { | |
| form.addEventListener('submit', function() { | |
| const resultDiv = document.querySelector('.result'); | |
| if (resultDiv) { | |
| resultDiv.style.display = 'none'; | |
| } | |
| loadingMessage.style.display = 'block'; | |
| }); | |
| } | |
| // 如果服务器返回了结果,隐藏加载提示 | |
| {% if result %} | |
| if (loadingMessage) { | |
| loadingMessage.style.display = 'none'; | |
| } | |
| {% endif %} | |
| // 初始化所有图表 | |
| initAllCharts(); | |
| }); | |
| // 切换考试记录标签页 | |
| function openTab(evt, tabName) { | |
| const tabcontents = document.getElementsByClassName("tab-content"); | |
| for (let i = 0; i < tabcontents.length; i++) { | |
| tabcontents[i].classList.remove("active"); | |
| } | |
| const tablinks = document.getElementsByClassName("tab-button"); | |
| for (let i = 0; i < tablinks.length; i++) { | |
| tablinks[i].classList.remove("active"); | |
| } | |
| document.getElementById(tabName).classList.add("active"); | |
| evt.currentTarget.classList.add("active"); | |
| // Re-initialize radar chart for the newly shown tab | |
| setTimeout(() => { | |
| const examId = tabName.replace('record-', ''); | |
| renderRadarChart(examId); | |
| }, 100); | |
| } | |
| // 渲染历史成绩排名统计 | |
| function renderStatistics() { | |
| const statsContainer = document.getElementById(`statistics-section`); | |
| if (!statsContainer) return; | |
| const allExams = collectAllExamData(); | |
| statsContainer.innerHTML = ''; | |
| if (allExams.length === 0) { | |
| const noDataP = document.createElement('p'); | |
| noDataP.className = 'no-stats-data'; | |
| noDataP.textContent = '无历史数据可供统计。'; | |
| statsContainer.appendChild(noDataP); | |
| return; | |
| } | |
| const sectionTitle = document.createElement('h4'); | |
| sectionTitle.textContent = '历史成绩排名统计'; | |
| statsContainer.appendChild(sectionTitle); | |
| const table = document.createElement('table'); | |
| table.className = 'statistics-table'; | |
| const thead = table.createTHead(); | |
| const headerRow = thead.insertRow(); | |
| ['科目/项目', '平均排名', '最高排名', '最低排名', '有效考试次数'].forEach(text => { | |
| const th = document.createElement('th'); | |
| th.textContent = text; | |
| headerRow.appendChild(th); | |
| }); | |
| const tbody = table.createTBody(); | |
| const itemsToStat = ['大类排名', '语文', '数学', '英语', '物理', '化学', '生物', '历史', '政治', '地理']; | |
| itemsToStat.forEach(item => { | |
| const ranks = []; | |
| allExams.forEach(exam => { | |
| let rankStr; | |
| if (item === '大类排名') { | |
| rankStr = exam.typeRanking; | |
| } else { | |
| rankStr = exam[`${item}校次`]; | |
| } | |
| if (rankStr && rankStr !== '无数据' && rankStr.trim() !== '') { | |
| const rankVal = parseInt(rankStr); | |
| if (!isNaN(rankVal)) { | |
| ranks.push(rankVal); | |
| } | |
| } | |
| }); | |
| if (ranks.length > 0) { | |
| const row = tbody.insertRow(); | |
| row.insertCell().textContent = item; | |
| const sum = ranks.reduce((acc, val) => acc + val, 0); | |
| const avg = (sum / ranks.length).toFixed(2); | |
| const minRank = Math.min(...ranks); | |
| const maxRank = Math.max(...ranks); | |
| row.insertCell().textContent = avg; | |
| row.insertCell().textContent = minRank; | |
| row.insertCell().textContent = maxRank; | |
| row.insertCell().textContent = ranks.length; | |
| } | |
| }); | |
| statsContainer.appendChild(table); | |
| } | |
| // 图表的全局配置 | |
| const chartOptions = { | |
| activeSubject: '总分', // 当前激活的科目 | |
| subjects: {}, | |
| colors: { | |
| '总分': 'rgb(255, 99, 132)', // 大类排名用红色 | |
| '语文': 'rgb(255, 159, 64)', | |
| '数学': 'rgb(75, 192, 192)', | |
| '英语': 'rgb(255, 206, 86)', | |
| '物理': 'rgb(153, 102, 255)', | |
| '化学': 'rgb(199, 199, 199)', | |
| '生物': 'rgb(83, 102, 255)', | |
| '历史': 'rgb(255, 99, 132)', | |
| '政治': 'rgb(54, 162, 235)', | |
| '地理': 'rgb(40, 159, 64)' | |
| } | |
| }; | |
| // 移除切换趋势图表视图函数,因为不再需要 | |
| // 初始化所有图表 | |
| function initAllCharts() { | |
| const allExams = collectAllExamData(); | |
| // 为每个标签页初始化雷达图 | |
| const tabContents = document.querySelectorAll('.tab-content'); | |
| tabContents.forEach(tab => { | |
| if (tab.id.startsWith('record-')) { | |
| const examId = tab.id.replace('record-', ''); | |
| renderRadarChart(examId); | |
| } | |
| }); | |
| if (allExams.length > 1) { | |
| initSubjectToggles(); | |
| renderTrendChart(); | |
| renderStatistics(); | |
| } | |
| } | |
| // 初始化科目选择器 | |
| function initSubjectToggles() { | |
| const allExams = collectAllExamData(); | |
| if (allExams.length < 2) return; | |
| const subjects = ['总分', '语文', '数学', '英语', '物理', '化学', '生物', '历史', '政治', '地理']; | |
| const availableSubjects = []; | |
| subjects.forEach(subject => { | |
| let hasData = false; | |
| if (subject === '总分') { | |
| hasData = allExams.some(exam => exam.typeRanking && exam.typeRanking !== '无数据'); | |
| } else { | |
| hasData = allExams.some(exam => exam[`${subject}校次`] && exam[`${subject}校次`] !== '无数据'); | |
| } | |
| if (hasData) { | |
| availableSubjects.push(subject); | |
| } | |
| }); | |
| const toggleContainer = document.getElementById(`subject-toggles`); | |
| if (!toggleContainer) return; | |
| toggleContainer.innerHTML = ''; | |
| availableSubjects.forEach(subject => { | |
| const toggle = document.createElement('div'); | |
| toggle.className = `subject-toggle ${subject === chartOptions.activeSubject ? 'active' : ''}`; | |
| toggle.dataset.subject = subject; | |
| const colorIndicator = document.createElement('span'); | |
| colorIndicator.className = 'color-indicator'; | |
| colorIndicator.style.backgroundColor = chartOptions.colors[subject]; | |
| toggle.appendChild(colorIndicator); | |
| toggle.appendChild(document.createTextNode(subject)); | |
| toggle.addEventListener('click', function() { | |
| // 移除所有active类 | |
| document.querySelectorAll('#subject-toggles .subject-toggle').forEach( | |
| t => t.classList.remove('active') | |
| ); | |
| // 激活当前选择 | |
| this.classList.add('active'); | |
| chartOptions.activeSubject = this.dataset.subject; | |
| renderTrendChart(); | |
| }); | |
| toggleContainer.appendChild(toggle); | |
| }); | |
| } | |
| // 渲染趋势图表 | |
| function renderTrendChart() { | |
| const chartCanvas = document.getElementById(`trend-chart-canvas`); | |
| if (!chartCanvas) return; | |
| const allExams = collectAllExamData(); | |
| if (allExams.length < 2) { | |
| const noDataElement = document.getElementById(`no-trend-data`); | |
| if (noDataElement) noDataElement.style.display = 'block'; | |
| chartCanvas.style.display = 'none'; | |
| return; | |
| } | |
| const noDataElement = document.getElementById(`no-trend-data`); | |
| if (noDataElement) noDataElement.style.display = 'none'; | |
| chartCanvas.style.display = 'block'; | |
| // 使用后端提供的排序索引进行排序 | |
| allExams.sort((a, b) => a.sortIndex - b.sortIndex); | |
| const labels = allExams.map(exam => formatExamName(exam.examName)); | |
| const subject = chartOptions.activeSubject; | |
| let data, label, color; | |
| if (subject === '总分') { | |
| data = allExams.map(exam => parseInt(exam.typeRanking) || null); | |
| label = '大类排名'; | |
| color = chartOptions.colors['总分']; | |
| } else { | |
| data = allExams.map(exam => { | |
| const rank = exam[`${subject}校次`]; | |
| return (rank && rank !== '无数据') ? parseInt(rank) : null; | |
| }); | |
| label = `${subject}校次`; | |
| color = chartOptions.colors[subject]; | |
| } | |
| if (charts.trendChart) { | |
| charts.trendChart.destroy(); | |
| } | |
| const ctx = chartCanvas.getContext('2d'); | |
| charts.trendChart = new Chart(ctx, { | |
| type: 'line', | |
| data: { | |
| labels: labels, | |
| datasets: [{ | |
| label: label, | |
| data: data, | |
| borderColor: color, | |
| backgroundColor: color.replace('rgb', 'rgba').replace(')', ', 0.1)'), | |
| tension: 0.3, | |
| fill: false, | |
| pointBackgroundColor: color, | |
| pointBorderColor: '#fff', | |
| pointHoverBackgroundColor: '#fff', | |
| pointHoverBorderColor: color, | |
| pointRadius: 5, | |
| pointHoverRadius: 7, | |
| borderWidth: 3 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| scales: { | |
| y: { | |
| reverse: true, | |
| title: { | |
| display: true, | |
| text: '排名', | |
| font: { | |
| size: 14, | |
| weight: 'bold' | |
| } | |
| }, | |
| grid: { | |
| color: 'rgba(0,0,0,0.1)' | |
| } | |
| }, | |
| x: { | |
| title: { | |
| display: true, | |
| text: '考试', | |
| font: { | |
| size: 14, | |
| weight: 'bold' | |
| } | |
| }, | |
| grid: { | |
| display: false | |
| } | |
| } | |
| }, | |
| plugins: { | |
| title: { | |
| display: true, | |
| text: `${subject} 排名趋势变化`, | |
| font: { | |
| size: 16, | |
| weight: 'bold' | |
| } | |
| }, | |
| legend: { | |
| display: false | |
| }, | |
| tooltip: { | |
| mode: 'index', | |
| intersect: false, | |
| backgroundColor: 'rgba(0,0,0,0.8)', | |
| titleColor: '#fff', | |
| bodyColor: '#fff', | |
| borderColor: color, | |
| borderWidth: 2, | |
| cornerRadius: 6 | |
| } | |
| }, | |
| elements: { | |
| line: { | |
| tension: 0.3 | |
| }, | |
| point: { | |
| radius: 5, | |
| hitRadius: 15, | |
| hoverRadius: 8 | |
| } | |
| }, | |
| animation: { | |
| duration: 1000, | |
| easing: 'easeInOutQuart' | |
| }, | |
| interaction: { | |
| intersect: false, | |
| mode: 'index' | |
| } | |
| } | |
| }); | |
| } | |
| // 移除原来的渲染排名趋势图表和渲染学科排名趋势图表函数 | |
| // 渲染雷达图 | |
| function renderRadarChart(examId) { | |
| const chartCanvas = document.getElementById(`radar-chart-${examId}`); | |
| if (!chartCanvas) return; | |
| const tabContent = document.getElementById(`record-${examId}`); | |
| if (!tabContent) return; | |
| const scoreTable = tabContent.querySelector('.score-table'); | |
| if (!scoreTable) return; | |
| const subjects = ['语文', '数学', '英语', '物理', '化学', '生物', '历史', '政治', '地理']; | |
| const data = []; | |
| const validSubjects = []; | |
| const tableRows = scoreTable.querySelectorAll('tbody tr'); | |
| tableRows.forEach(row => { | |
| const cells = row.querySelectorAll('td'); | |
| if (cells.length >= 3) { | |
| const subject = cells[0].textContent.trim(); | |
| const rank = cells[2].textContent.trim(); | |
| if (subjects.includes(subject) && rank && rank !== '-' && rank !== '无数据') { | |
| const rankNum = parseInt(rank); | |
| if (!isNaN(rankNum)) { | |
| data.push(rankNum); | |
| validSubjects.push(subject); | |
| } | |
| } | |
| } | |
| }); | |
| if (validSubjects.length < 3) { | |
| const noDataElement = document.getElementById(`no-radar-data-${examId}`); | |
| if (noDataElement) noDataElement.style.display = 'block'; | |
| chartCanvas.style.display = 'none'; | |
| return; | |
| } | |
| const noDataElement = document.getElementById(`no-radar-data-${examId}`); | |
| if (noDataElement) noDataElement.style.display = 'none'; | |
| chartCanvas.style.display = 'block'; | |
| const chartKey = `radarChart${examId}`; | |
| if (charts[chartKey]) { | |
| charts[chartKey].destroy(); | |
| } | |
| const ctx = chartCanvas.getContext('2d'); | |
| charts[chartKey] = new Chart(ctx, { | |
| type: 'radar', | |
| data: { | |
| labels: validSubjects, | |
| datasets: [{ | |
| label: '校内排名', | |
| data: data, | |
| backgroundColor: 'rgba(67, 97, 238, 0.3)', | |
| borderColor: 'rgb(67, 97, 238)', | |
| pointBackgroundColor: 'rgb(67, 97, 238)', | |
| pointBorderColor: '#fff', | |
| pointHoverBackgroundColor: '#fff', | |
| pointHoverBorderColor: 'rgb(67, 97, 238)', | |
| borderWidth: 2 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| scales: { | |
| r: { | |
| angleLines: { | |
| display: true | |
| }, | |
| suggestedMin: 0, | |
| ticks: { | |
| display: false | |
| }, | |
| pointLabels: { | |
| font: { | |
| size: 12, | |
| weight: 'bold' | |
| } | |
| }, | |
| reverse: true | |
| } | |
| }, | |
| plugins: { | |
| legend: { | |
| display: false | |
| }, | |
| tooltip: { | |
| callbacks: { | |
| label: function(context) { | |
| return `${context.label}: 排名 ${context.parsed.r}`; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| // 收集所有考试数据 | |
| function collectAllExamData() { | |
| if (examDataCache) return examDataCache; | |
| const allExams = []; | |
| {% if result and result.scores_data %} | |
| const examData = [ | |
| {% for record in result.scores_data %} | |
| { | |
| examName: "{{ record.get('考试名称', '未知')|escape }}", | |
| examTime: "{{ record.get('考试时间', '')|escape }}", | |
| sortIndex: {{ record.get('sort_index', 0) }}, | |
| typeRanking: "{{ record.get('大类排名', '')|escape }}", | |
| comboRanking: "{{ record.get('组合排名', '')|escape }}", | |
| "语文": "{{ record.get('语文', '无数据')|escape }}", | |
| "语文校次": "{{ record.get('语文校次', '无数据')|escape }}", | |
| "数学": "{{ record.get('数学', '无数据')|escape }}", | |
| "数学校次": "{{ record.get('数学校次', '无数据')|escape }}", | |
| "英语": "{{ record.get('英语', '无数据')|escape }}", | |
| "英语校次": "{{ record.get('英语校次', '无数据')|escape }}", | |
| "物理": "{{ record.get('物理', '无数据')|escape }}", | |
| "物理校次": "{{ record.get('物理校次', '无数据')|escape }}", | |
| "化学": "{{ record.get('化学', '无数据')|escape }}", | |
| "化学校次": "{{ record.get('化学校次', '无数据')|escape }}", | |
| "生物": "{{ record.get('生物', '无数据')|escape }}", | |
| "生物校次": "{{ record.get('生物校次', '无数据')|escape }}", | |
| "历史": "{{ record.get('历史', '无数据')|escape }}", | |
| "历史校次": "{{ record.get('历史校次', '无数据')|escape }}", | |
| "政治": "{{ record.get('政治', '无数据')|escape }}", | |
| "政治校次": "{{ record.get('政治校次', '无数据')|escape }}", | |
| "地理": "{{ record.get('地理', '无数据')|escape }}", | |
| "地理校次": "{{ record.get('地理校次', '无数据')|escape }}" | |
| }{% if not loop.last %},{% endif %} | |
| {% endfor %} | |
| ]; | |
| allExams.push(...examData); | |
| {% endif %} | |
| examDataCache = allExams; | |
| return allExams; | |
| } | |
| // 格式化考试名称 | |
| function formatExamName(name) { | |
| if (!name) return "未知"; | |
| if (name.length > 10) { | |
| return name.substring(0, 10) + '...'; | |
| } | |
| return name; | |
| } | |
| </script> | |
| </body> | |
| </html> | |