grade_query / src /templates /compare.html
jzyg123's picture
Update src/templates/compare.html
88be809 verified
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>学生成绩比较系统</title>
<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">
<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;
/* 新增对比颜色 */
--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);
}
* {
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: 1200px;
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;
}
.nav-links {
text-align: center;
margin-bottom: 20px;
}
.nav-links a {
color: var(--primary-color);
text-decoration: none;
margin: 0 15px;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 5px;
padding: 8px 16px;
border-radius: 20px;
transition: var(--transition);
}
.nav-links a:hover {
background-color: rgba(67, 97, 238, 0.1);
}
.card {
background-color: var(--card-bg);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
padding: 25px;
margin-bottom: 30px;
transition: var(--transition);
}
.student-selection {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-bottom: 30px;
}
.student-panel {
border: 2px solid #e1e5ea;
border-radius: var(--border-radius);
padding: 20px;
position: relative;
transition: all 0.3s ease;
}
.student-panel.active-a {
border-color: var(--student-a-color);
background-color: rgba(67, 97, 238, 0.05);
}
.student-panel.active-b {
border-color: var(--student-b-color);
background-color: rgba(255, 107, 107, 0.05);
}
.student-panel h3 {
margin-bottom: 20px;
text-align: center;
}
.student-panel h3.student-a {
color: var(--student-a-color);
}
.student-panel h3.student-b {
color: var(--student-b-color);
}
.search-container {
position: relative;
margin-bottom: 15px;
}
.search-input {
width: 100%;
padding: 12px 20px;
padding-left: 45px;
border: 2px solid #e1e5ea;
border-radius: 25px;
font-size: 14px;
transition: var(--transition);
outline: none;
}
.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: 15px;
top: 50%;
transform: translateY(-50%);
color: #aab0b7;
font-size: 18px;
}
.student-list {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
margin-top: 10px;
max-height: 200px;
overflow-y: auto;
display: none;
position: absolute;
top: 100%;
left: 0;
right: 0;
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: 12px 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: 3px;
pointer-events: none;
}
.student-id {
color: #777;
font-size: 12px;
pointer-events: none;
}
.selected-student {
background-color: #f8faff;
border: 1px solid #e1e5ea;
border-radius: 8px;
padding: 15px;
margin-top: 10px;
display: none;
transition: all 0.3s ease;
animation: fadeIn 0.5s ease;
}
.selected-student.student-a {
border-color: var(--student-a-color);
background-color: var(--student-a-light);
}
.selected-student.student-b {
border-color: var(--student-b-color);
background-color: var(--student-b-light);
}
.selected-student.show {
display: block;
}
.compare-btn {
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 25px;
padding: 12px 30px;
font-size: 16px;
cursor: pointer;
transition: var(--transition);
font-weight: 600;
display: block;
margin: 20px auto;
min-width: 150px;
}
.compare-btn:hover:not(:disabled) {
background-color: var(--primary-hover);
transform: translateY(-2px);
}
.compare-btn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.comparison-section {
display: none;
}
.comparison-section.show {
display: block;
animation: fadeIn 0.5s ease;
}
/* 优化考试选择部分 */
.exam-selector {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
background-color: #f8f9fa;
border-radius: 12px;
padding: 15px;
max-height: 200px;
overflow-y: auto;
}
.exam-btn {
padding: 8px 16px;
border: 2px solid #e1e5ea;
background: white;
border-radius: 20px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
font-weight: 600;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
margin-bottom: 5px;
}
.exam-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 10px rgba(0,0,0,0.1);
}
.exam-btn.active {
border-color: var(--primary-color);
background-color: var(--primary-color);
color: white;
}
.comparison-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);
table-layout: fixed; /* 固定表格布局 */
}
.comparison-table th,
.comparison-table td {
border: 1px solid #eaecef;
padding: 12px 15px;
text-align: center;
word-wrap: break-word; /* 允许长内容换行 */
overflow-wrap: break-word;
}
.comparison-table th {
background-color: #f6f8fa;
font-weight: 600;
color: #444;
position: sticky;
top: 0;
z-index: 1;
}
.comparison-table tr:nth-child(even) {
background-color: #fafbff;
}
.better-score {
background-color: rgba(40, 167, 69, 0.05); /* 降低透明度防止色彩干扰 */
color: var(--success-color);
font-weight: 600;
position: relative;
border-left: 3px solid var(--success-color); /* 添加边框增强视觉效果 */
}
.worse-score {
background-color: rgba(220, 53, 69, 0.05); /* 降低透明度防止色彩干扰 */
color: var(--danger-color);
font-weight: 600;
position: relative;
border-left: 3px solid var(--danger-color); /* 添加边框增强视觉效果 */
}
/* 排名差距指示器 */
.rank-diff-indicator {
height: 4px;
background-color: #e9ecef;
border-radius: 2px;
margin-top: 5px;
position: relative;
overflow: hidden;
}
.rank-diff-bar {
height: 100%;
position: absolute;
top: 0;
left: 0;
border-radius: 2px;
transition: width 0.6s ease;
}
.rank-diff-bar.better {
background-color: var(--success-color);
}
.rank-diff-bar.worse {
background-color: var(--danger-color);
}
.chart-container {
width: 100%;
height: 400px;
margin: 20px 0;
position: relative;
}
/* 图表容器布局 */
.charts-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin: 30px 0;
}
/* 移动设备优化 */
@media (max-width: 992px) {
.charts-row {
grid-template-columns: 1fr;
}
}
@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;
}
.nav-links {
margin-bottom: 15px;
}
.nav-links a {
margin: 0 8px;
padding: 6px 12px;
font-size: 14px;
}
.card {
padding: 20px 15px;
margin-bottom: 20px;
}
/* 学生选择面板移动端布局 */
.student-selection {
grid-template-columns: 1fr;
gap: 20px;
margin-bottom: 20px;
}
.student-panel {
padding: 15px;
}
.student-panel h3 {
font-size: 1.2rem;
margin-bottom: 15px;
}
.search-input {
padding: 10px 18px;
padding-left: 40px;
font-size: 16px; /* 防止iOS缩放 */
border-radius: 20px;
}
.search-icon {
left: 12px;
}
/* 学生列表移动端优化 */
.student-list {
max-height: 250px; /* 增加高度 */
-webkit-overflow-scrolling: touch;
overflow-y: auto;
position: absolute; /* 改回 absolute */
top: 100%;
left: 0;
right: 0;
z-index: 10000; /* 提高层级 */
background-color: white;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
/* 修复移动设备滚动问题 */
touch-action: pan-y;
overscroll-behavior: contain;
}
.student-item {
padding: 10px 12px;
}
.student-name {
font-size: 14px;
}
.student-id {
font-size: 11px;
}
.selected-student {
padding: 12px;
margin-top: 8px;
}
.compare-btn {
padding: 10px 25px;
font-size: 15px;
margin: 15px auto;
}
/* 考试选择器移动端优化 */
.exam-selector {
max-height: 120px;
padding: 12px;
gap: 8px;
}
.exam-btn {
padding: 6px 12px;
font-size: 13px;
margin-bottom: 4px;
}
/* 对比表格移动端优化 */
.comparison-table {
font-size: 12px;
margin: 15px 0;
}
.comparison-table th,
.comparison-table td {
padding: 8px 6px;
word-wrap: break-word;
max-width: 100px;
}
.comparison-table th {
font-size: 11px;
}
/* 亮点卡片移动端优化 */
.comparison-highlights {
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 15px;
margin: 20px 0;
}
.highlight-card {
padding: 15px;
}
.highlight-title {
font-size: 0.8rem;
}
.highlight-value {
font-size: 1.4rem;
}
.highlight-sub {
font-size: 0.8rem;
}
/* 图表容器移动端优化 */
.chart-card {
height: 300px;
padding: 15px;
overflow: hidden; /* 防止内容溢出 */
}
.chart-card h4 {
font-size: 1rem;
margin-bottom: 15px;
}
.chart-area {
min-height: 200px;
max-height: 220px;
width: 100%;
overflow: hidden; /* 防止图表超出边界 */
position: relative;
}
/* 强制图表在移动设备上响应式缩放 */
.chart-area canvas {
max-width: 100% !important;
max-height: 100% !important;
width: auto !important;
height: auto !important;
}
.chart-legend {
margin-top: 10px;
gap: 15px;
}
.legend-item {
font-size: 12px;
}
/* 标签按钮移动端优化 */
.tabs-header {
margin-bottom: 15px;
}
.tab-button {
padding: 10px 15px;
font-size: 14px;
}
/* 科目选择器移动端优化 */
.subject-toggles {
gap: 6px;
margin: 12px 0;
}
.subject-toggle {
padding: 5px 10px;
font-size: 12px;
}
/* 排名差距指示器移动端优化 */
.rank-diff-indicator {
height: 3px;
margin-top: 3px;
}
}
@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;
}
.student-panel {
padding: 12px;
}
.student-panel h3 {
font-size: 1.1rem;
margin-bottom: 12px;
}
.search-input {
padding: 8px 15px;
padding-left: 35px;
font-size: 16px;
}
.search-icon {
left: 10px;
font-size: 16px;
}
/* 更小屏幕的学生列表优化 */
.student-list {
max-height: 200px;
/* 强化移动设备滚动支持 */
overflow-y: auto;
-webkit-overflow-scrolling: touch;
touch-action: pan-y;
overscroll-behavior-y: contain;
position: absolute;
z-index: 10000;
}
.student-item {
padding: 8px 10px;
}
.student-name {
font-size: 13px;
}
.student-id {
font-size: 10px;
}
.selected-student {
padding: 10px;
}
.compare-btn {
padding: 8px 20px;
font-size: 14px;
min-width: 120px;
}
/* 超小屏幕表格优化 */
.comparison-table {
font-size: 10px;
display: block;
overflow-x: auto;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
}
.comparison-table thead,
.comparison-table tbody,
.comparison-table th,
.comparison-table td,
.comparison-table tr {
display: block;
}
.comparison-table thead tr {
position: absolute;
top: -9999px;
left: -9999px;
}
.comparison-table tr {
border: 1px solid #ccc;
margin-bottom: 10px;
padding: 10px;
border-radius: 8px;
background: white;
}
.comparison-table td {
border: none;
position: relative;
padding: 8px 8px 8px 50%;
text-align: left;
white-space: normal;
max-width: none;
}
.comparison-table td:before {
content: attr(data-label) ": ";
position: absolute;
left: 6px;
width: 45%;
padding-right: 10px;
white-space: nowrap;
font-weight: 600;
color: #333;
}
/* 亮点卡片超小屏幕优化 */
.comparison-highlights {
grid-template-columns: 1fr 1fr;
gap: 10px;
margin: 15px 0;
}
.highlight-card {
padding: 12px;
}
.highlight-title {
font-size: 0.75rem;
}
.highlight-value {
font-size: 1.2rem;
}
.highlight-sub {
font-size: 0.75rem;
}
/* 图表容器超小屏幕优化 */
.chart-card {
height: 250px;
padding: 12px;
}
.chart-card h4 {
font-size: 0.9rem;
margin-bottom: 12px;
}
.chart-area {
min-height: 160px;
max-height: 180px;
}
.chart-legend {
margin-top: 8px;
gap: 10px;
}
.legend-item {
font-size: 11px;
}
.legend-color {
width: 8px;
height: 8px;
}
/* 标签优化 */
.tab-button {
padding: 8px 10px;
font-size: 12px;
}
/* 科目选择器超小屏幕优化 */
.subject-toggle {
padding: 4px 8px;
font-size: 11px;
}
/* 考试选择器超小屏幕优化 */
.exam-selector {
max-height: 100px;
padding: 10px;
}
.exam-btn {
padding: 5px 10px;
font-size: 12px;
}
}
/* 触摸设备滚动优化 */
@media (pointer: coarse) {
.student-list,
.exam-selector,
.comparison-table {
-webkit-overflow-scrolling: touch;
scrollbar-width: thin;
}
.student-list::-webkit-scrollbar,
.exam-selector::-webkit-scrollbar {
width: 4px;
}
.student-list::-webkit-scrollbar-track,
.exam-selector::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 2px;
}
.student-list::-webkit-scrollbar-thumb,
.exam-selector::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 2px;
}
.student-list::-webkit-scrollbar-thumb:hover,
.exam-selector::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
}
.chart-card {
background-color: var(--white);
border-radius: var(--border-radius);
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
padding: 20px;
height: 400px; /* 降低高度,从450px改为400px */
display: flex;
flex-direction: column;
}
.chart-card h4 {
margin: 0 0 20px 0;
color: var(--dark-color);
font-size: 1.1rem;
text-align: center;
}
.chart-area {
flex: 1;
position: relative;
min-height: 280px; /* 添加最小高度确保图表显示 */
max-height: 320px; /* 限制最大高度 */
width: 100%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
/* 图例样式 */
.chart-legend {
display: flex;
justify-content: center;
margin-top: 15px;
gap: 20px;
}
.legend-item {
display: flex;
align-items: center;
font-size: 14px;
}
.legend-color {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 5px;
}
/* 动画效果 */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.comparison-highlights {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin: 30px 0;
}
.highlight-card {
background-color: var(--white);
border-radius: var(--border-radius);
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
padding: 20px;
text-align: center;
transition: all 0.3s ease;
}
.highlight-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
}
.highlight-title {
font-size: 0.9rem;
color: var(--secondary-color);
margin-bottom: 10px;
}
.highlight-value {
font-size: 2rem;
font-weight: 700;
margin-bottom: 5px;
}
.highlight-sub {
font-size: 0.9rem;
color: var(--secondary-color);
margin-bottom: 15px;
}
.highlight-card.student-a .highlight-value {
color: var(--student-a-color);
}
.highlight-card.student-b .highlight-value {
color: var(--student-b-color);
}
/* 科目选择器 */
.subject-toggles {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 15px 0;
justify-content: center;
}
.subject-toggle {
padding: 6px 12px;
border-radius: 20px;
font-size: 13px;
background-color: #f0f2f8;
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid transparent;
}
.subject-toggle:hover {
background-color: #e9ecf1;
}
.subject-toggle.active {
background-color: var(--primary-color);
color: white;
}
.tab-container {
margin: 20px 0;
}
.tabs-header {
display: flex;
border-bottom: 2px solid #eaecef;
margin-bottom: 20px;
}
.tab-button {
padding: 12px 20px;
background: transparent;
border: none;
cursor: pointer;
font-weight: 600;
color: var(--secondary-color);
position: relative;
transition: all 0.2s ease;
}
.tab-button:after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 100%;
height: 3px;
background-color: var(--primary-color);
transform: scaleX(0);
transition: transform 0.3s ease;
}
.tab-button.active {
color: var(--primary-color);
}
.tab-button.active:after {
transform: scaleX(1);
}
.tab-content {
display: none;
animation: fadeIn 0.4s ease;
}
.tab-content.active {
display: block;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>学生成绩比较系统</h1>
<p>选择两名学生进行成绩对比分析</p>
</div>
<div class="nav-links">
<a href="/"><span class="material-icons">home</span>返回查询页面</a>
</div>
<div class="card">
<div class="student-selection">
<div class="student-panel" id="student-panel-a">
<h3 class="student-a">学生 A</h3>
<div class="search-container">
<span class="material-icons search-icon">search</span>
<input type="text" id="search-input-a" class="search-input" placeholder="输入学生姓名搜索...">
<div class="student-list" id="student-list-a"></div>
</div>
<div class="selected-student student-a" id="selected-student-a"></div>
</div>
<div class="student-panel" id="student-panel-b">
<h3 class="student-b">学生 B</h3>
<div class="search-container">
<span class="material-icons search-icon">search</span>
<input type="text" id="search-input-b" class="search-input" placeholder="输入学生姓名搜索...">
<div class="student-list" id="student-list-b"></div>
</div>
<div class="selected-student student-b" id="selected-student-b"></div>
</div>
</div>
<button id="compare-btn" class="compare-btn" onclick="startComparison()" disabled>开始比较</button>
</div>
<!-- 比较结果区域 -->
<div class="comparison-section" id="comparison-section">
<div class="card">
<h3>考试选择</h3>
<div class="exam-selector" id="exam-selector">
<!-- 动态生成考试选择按钮 -->
</div>
</div>
<!-- 主要比较结果卡片 -->
<div class="card" id="comparison-results">
<!-- 比较结果将在这里显示 -->
</div>
<!-- 可视化图表区域 -->
<div class="card">
<div class="tab-container">
<div class="tabs-header">
<button class="tab-button active" data-tab="tab-charts">综合图表分析</button>
<button class="tab-button" data-tab="tab-trends">成绩趋势对比</button>
</div>
<!-- 图表标签内容 -->
<div id="tab-charts" class="tab-content active">
<div class="charts-row">
<!-- 雷达图 -->
<div class="chart-card">
<h4>各科能力对比雷达图</h4>
<div class="chart-area">
<canvas id="radar-chart"></canvas>
</div>
<div class="chart-legend">
<div class="legend-item">
<div class="legend-color" style="background-color: var(--student-a-color)"></div>
<span id="legend-name-a">学生A</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: var(--student-b-color)"></div>
<span id="legend-name-b">学生B</span>
</div>
</div>
</div>
<!-- 柱状图 -->
<div class="chart-card">
<h4>历史平均排名对比</h4>
<div class="subject-toggles" id="subject-toggles-bar">
<!-- 科目选择器将在JS中动态生成 -->
</div>
<div class="chart-area">
<canvas id="bar-chart"></canvas>
</div>
</div>
</div>
</div>
<!-- 趋势标签内容 -->
<div id="tab-trends" class="tab-content">
<div class="subject-toggles" id="subject-toggles-trend">
<!-- 科目选择器将在JS中动态生成 -->
</div>
<div class="chart-card" style="height: 450px;"> <!-- 趋势图表高度单独设置 -->
<h4>排名趋势对比</h4>
<div class="chart-area" style="min-height: 350px; max-height: 380px;">
<canvas id="trend-chart"></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
let studentsDataA = null;
let studentsDataB = null;
let selectedStudentA = null;
let selectedStudentB = null;
let activeSubject = '总分'; // 默认显示总分对比
let charts = {}; // 存储所有图表实例
document.addEventListener('DOMContentLoaded', function() {
setupStudentSearch('a');
setupStudentSearch('b');
setupTabs();
});
// 设置标签切换
function setupTabs() {
const tabButtons = document.querySelectorAll('.tab-button');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
const tabId = button.dataset.tab;
// 移除所有激活类
document.querySelectorAll('.tab-button').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
// 激活当前标签
button.classList.add('active');
document.getElementById(tabId).classList.add('active');
// 重新绘制图表
if (tabId === 'tab-trends' && charts.trendChart) {
setTimeout(() => {
charts.trendChart.resize();
}, 100);
}
});
});
}
function setupStudentSearch(student) {
const searchInput = document.getElementById(`search-input-${student}`);
const studentList = document.getElementById(`student-list-${student}`);
let typingTimer;
if (!searchInput || !studentList) {
console.error(`❌ 无法找到学生${student}的搜索元素`);
return;
}
searchInput.addEventListener('input', function() {
clearTimeout(typingTimer);
const inputValue = this.value.trim();
console.log(`📝 学生${student}输入: "${inputValue}"`);
if (inputValue) {
typingTimer = setTimeout(() => {
console.log(`⏰ 开始搜索学生${student}: "${inputValue}"`);
searchStudents(student, inputValue);
}, 500);
} else {
studentList.style.display = 'none';
}
});
// 点击外部关闭列表
document.addEventListener('click', function(event) {
if (!searchInput.contains(event.target) && !studentList.contains(event.target)) {
studentList.style.display = 'none';
}
});
}
function searchStudents(student, name) {
console.log(`🔍 开始搜索学生: ${name} (学生${student})`);
const studentList = document.getElementById(`student-list-${student}`);
studentList.innerHTML = '<div style="text-align:center;padding:15px;">正在搜索...</div>';
studentList.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) {
studentList.innerHTML = '';
data.students.forEach((studentInfo, index) => {
const div = document.createElement('div');
div.className = 'student-item';
div.innerHTML = `
<div class="student-name">${studentInfo.姓名}</div>
<div class="student-id">${studentInfo.身份证号}</div>
`;
div.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
// 统一用身份证号字段
selectStudent(student, {
...studentInfo,
'显示身份证号': studentInfo.身份证号
});
studentList.style.display = 'none';
});
// 移动端触摸优化
div.addEventListener('touchstart', function(e) {
e.stopPropagation();
}, { passive: true });
studentList.appendChild(div);
});
} else {
studentList.innerHTML = '<div style="text-align:center;padding:15px;color:#888;">未找到匹配的学生</div>';
}
})
.catch(error => {
studentList.innerHTML = '<div style="text-align:center;padding:15px;color:red;">搜索出错,请稍后再试</div>';
});
}
function selectStudent(student, studentInfo) {
if (student === 'a') {
selectedStudentA = studentInfo;
const selectedDiv = document.getElementById('selected-student-a');
selectedDiv.innerHTML = `
<div class="student-name">${studentInfo.姓名}</div>
<div class="student-id">${studentInfo.显示身份证号}</div>
`;
selectedDiv.classList.add('show');
document.getElementById('student-panel-a').classList.add('active-a');
document.getElementById('legend-name-a').textContent = studentInfo.姓名;
// 重置数据
studentsDataA = null;
} else {
selectedStudentB = studentInfo;
const selectedDiv = document.getElementById('selected-student-b');
selectedDiv.innerHTML = `
<div class="student-name">${studentInfo.姓名}</div>
<div class="student-id">${studentInfo.显示身份证号}</div>
`;
selectedDiv.classList.add('show');
document.getElementById('student-panel-b').classList.add('active-b');
document.getElementById('legend-name-b').textContent = studentInfo.姓名;
// 重置数据
studentsDataB = null;
}
// 检查是否可以开始比较
const compareBtn = document.getElementById('compare-btn');
if (selectedStudentA && selectedStudentB) {
compareBtn.disabled = false;
}
// 隐藏比较结果区域
const comparisonSection = document.getElementById('comparison-section');
if (comparisonSection) {
comparisonSection.classList.remove('show');
}
}
async function startComparison() {
const compareBtn = document.getElementById('compare-btn');
compareBtn.textContent = '正在加载数据...';
compareBtn.disabled = true;
try {
// 清理旧图表
Object.keys(charts).forEach(key => {
if (charts[key] && typeof charts[key].destroy === 'function') {
charts[key].destroy();
}
});
charts = {};
// 获取两个学生的成绩数据
const [dataA, dataB] = await Promise.all([
getStudentScores(selectedStudentA.身份证号),
getStudentScores(selectedStudentB.身份证号)
]);
if (dataA.success && dataB.success) {
studentsDataA = dataA.data;
studentsDataB = dataB.data;
showComparisonSection();
} else {
showError(`获取成绩数据失败: ${dataA.error || dataB.error}`);
}
} catch (error) {
showError(`加载数据时出错: ${error.message}`);
}
compareBtn.textContent = '开始比较';
compareBtn.disabled = false;
}
async function getStudentScores(idNumber) {
const formData = new FormData();
formData.append('id_number', idNumber);
const response = await fetch('/api/get_student_scores', {
method: 'POST',
body: formData
});
return await response.json();
}
function showComparisonSection() {
const examSelector = document.getElementById('exam-selector');
examSelector.innerHTML = '';
// 添加考试选择说明
const selectionTitle = document.createElement('p');
selectionTitle.style.width = '100%';
selectionTitle.style.marginBottom = '10px';
selectionTitle.style.fontWeight = '600';
selectionTitle.style.color = '#666';
selectionTitle.textContent = '选择要比较的考试:';
examSelector.appendChild(selectionTitle);
// 创建"历史所有考试"按钮
const allExamsBtn = document.createElement('button');
allExamsBtn.className = 'exam-btn active';
allExamsBtn.textContent = '历史所有考试对比';
allExamsBtn.onclick = () => {
setActiveExamBtn(allExamsBtn);
showHistoricalComparison();
};
examSelector.appendChild(allExamsBtn);
// 获取所有考试列表并排序(按时间倒序)
const allExams = [...studentsDataA, ...studentsDataB];
const uniqueExams = [...new Map(allExams.map(exam => [exam.考试名称, exam])).values()]
.sort((a, b) => (b.sort_index || 0) - (a.sort_index || 0));
uniqueExams.forEach((exam, index) => {
const examBtn = document.createElement('button');
examBtn.className = 'exam-btn';
// 添加日期信息
const examDate = exam.考试时间 ? ` (${exam.考试时间.split(' ')[0]})` : '';
examBtn.textContent = (exam.考试名称 || `考试${index + 1}`) + examDate;
examBtn.onclick = () => {
setActiveExamBtn(examBtn);
showSingleExamComparison(exam.考试名称);
};
examSelector.appendChild(examBtn);
});
document.getElementById('comparison-section').classList.add('show');
showHistoricalComparison(); // 默认显示历史对比
initCharts(); // 初始化图表
}
function setActiveExamBtn(activeBtn) {
document.querySelectorAll('.exam-btn').forEach(btn => btn.classList.remove('active'));
activeBtn.classList.add('active');
}
function showSingleExamComparison(examName) {
const examA = studentsDataA.find(exam => exam.考试名称 === examName);
const examB = studentsDataB.find(exam => exam.考试名称 === examName);
const resultsDiv = document.getElementById('comparison-results');
if (!examA || !examB) {
resultsDiv.innerHTML = '<div class="info-message">其中一名学生没有该考试的成绩记录</div>';
return;
}
const subjects = ['语文', '数学', '英语', '物理', '化学', '生物', '历史', '政治', '地理'];
// 创建亮点数据
let highlights = createExamHighlights(examA, examB);
let tableHTML = `
<h3>${examName} - 单次考试对比</h3>
<div class="comparison-highlights">
${highlights}
</div>
<table class="comparison-table">
<thead>
<tr>
<th>科目</th>
<th>${selectedStudentA.姓名} (成绩/排名)</th>
<th>${selectedStudentB.姓名} (成绩/排名)</th>
<th>对比结果</th>
</tr>
</thead>
<tbody>
`;
subjects.forEach(subject => {
const scoreA = examA[subject] || '无数据';
const rankA = examA[`${subject}校次`] || '无数据';
const scoreB = examB[subject] || '无数据';
const rankB = examB[`${subject}校次`] || '无数据';
if (scoreA !== '无数据' || scoreB !== '无数据') {
let comparison = '';
let classA = '', classB = '';
let diffIndicator = '';
if (rankA !== '无数据' && rankB !== '无数据') {
const rankNumA = parseInt(rankA);
const rankNumB = parseInt(rankB);
const diff = Math.abs(rankNumA - rankNumB);
const maxRank = Math.max(rankNumA, rankNumB) * 1.5; // 用于计算差距比例
const diffPercent = Math.min(100, Math.round((diff / maxRank) * 100));
if (rankNumA < rankNumB) {
comparison = `${selectedStudentA.姓名} 领先 ${diff} 名`;
classA = 'better-score';
classB = 'worse-score';
diffIndicator = `
<div class="rank-diff-indicator">
<div class="rank-diff-bar better" style="width: ${diffPercent}%"></div>
</div>
`;
} else if (rankNumA > rankNumB) {
comparison = `${selectedStudentB.姓名} 领先 ${diff} 名`;
classA = 'worse-score';
classB = 'better-score';
diffIndicator = `
<div class="rank-diff-indicator">
<div class="rank-diff-bar worse" style="width: ${diffPercent}%"></div>
</div>
`;
} else {
comparison = '排名相同';
}
}
tableHTML += `
<tr>
<td data-label="科目">${subject}</td>
<td data-label="${selectedStudentA.姓名}" class="${classA}">${scoreA} / ${rankA}</td>
<td data-label="${selectedStudentB.姓名}" class="${classB}">${scoreB} / ${rankB}</td>
<td data-label="对比结果">
${comparison}
${diffIndicator}
</td>
</tr>
`;
}
});
// 总分对比
const totalA = examA['总分'] || '无数据';
const totalRankA = examA['大类排名'] || '无数据';
const totalB = examB['总分'] || '无数据';
const totalRankB = examB['大类排名'] || '无数据';
let totalComparison = '';
let totalClassA = '', totalClassB = '';
let totalDiffIndicator = '';
if (totalRankA !== '无数据' && totalRankB !== '无数据') {
const rankNumA = parseInt(totalRankA);
const rankNumB = parseInt(totalRankB);
const diff = Math.abs(rankNumA - rankNumB);
const maxRank = Math.max(rankNumA, rankNumB) * 1.5;
const diffPercent = Math.min(100, Math.round((diff / maxRank) * 100));
if (rankNumA < rankNumB) {
totalComparison = `${selectedStudentA.姓名} 领先 ${diff} 名`;
totalClassA = 'better-score';
totalClassB = 'worse-score';
totalDiffIndicator = `
<div class="rank-diff-indicator">
<div class="rank-diff-bar better" style="width: ${diffPercent}%"></div>
</div>
`;
} else if (rankNumA > rankNumB) {
totalComparison = `${selectedStudentB.姓名} 领先 ${diff} 名`;
totalClassA = 'worse-score';
totalClassB = 'better-score';
totalDiffIndicator = `
<div class="rank-diff-indicator">
<div class="rank-diff-bar worse" style="width: ${diffPercent}%"></div>
</div>
`;
} else {
totalComparison = '排名相同';
}
}
tableHTML += `
<tr>
<td data-label="科目"><strong>总分</strong></td>
<td data-label="${selectedStudentA.姓名}" class="${totalClassA}"><strong>${totalA} / ${totalRankA}</strong></td>
<td data-label="${selectedStudentB.姓名}" class="${totalClassB}"><strong>${totalB} / ${totalRankB}</strong></td>
<td data-label="对比结果">
<strong>${totalComparison}</strong>
${totalDiffIndicator}
</td>
</tr>
</tbody>
</table>
`;
resultsDiv.innerHTML = tableHTML;
// 更新单次考试的雷达图
updateRadarChart(examA, examB);
}
// 创建考试亮点数据
function createExamHighlights(examA, examB) {
// 总分对比
const totalScoreA = examA['总分'] !== '无数据' ? parseFloat(examA['总分']) : null;
const totalScoreB = examB['总分'] !== '无数据' ? parseFloat(examB['总分']) : null;
// 总排名对比
const totalRankA = examA['大类排名'] !== '无数据' ? parseInt(examA['大类排名']) : null;
const totalRankB = examB['大类排名'] !== '无数据' ? parseInt(examB['大类排名']) : null;
// 找出A学生最好的科目
let bestSubjectA = '无';
let bestRankA = Infinity;
// 找出B学生最好的科目
let bestSubjectB = '无';
let bestRankB = Infinity;
const subjects = ['语文', '数学', '英语', '物理', '化学', '生物', '历史', '政治', '地理'];
subjects.forEach(subject => {
const rankKeyA = `${subject}校次`;
if (examA[rankKeyA] && examA[rankKeyA] !== '无数据') {
const rank = parseInt(examA[rankKeyA]);
if (rank < bestRankA) {
bestRankA = rank;
bestSubjectA = subject;
}
}
const rankKeyB = `${subject}校次`;
if (examB[rankKeyB] && examB[rankKeyB] !== '无数据') {
const rank = parseInt(examB[rankKeyB]);
if (rank < bestRankB) {
bestRankB = rank;
bestSubjectB = subject;
}
}
});
return `
<div class="highlight-card student-a">
<div class="highlight-title">总分</div>
<div class="highlight-value">${totalScoreA !== null ? totalScoreA : 'N/A'}</div>
<div class="highlight-sub">${selectedStudentA.姓名}</div>
</div>
<div class="highlight-card student-b">
<div class="highlight-title">总分</div>
<div class="highlight-value">${totalScoreB !== null ? totalScoreB : 'N/A'}</div>
<div class="highlight-sub">${selectedStudentB.姓名}</div>
</div>
<div class="highlight-card student-a">
<div class="highlight-title">总排名</div>
<div class="highlight-value">${totalRankA !== null ? totalRankA : 'N/A'}</div>
<div class="highlight-sub">${selectedStudentA.姓名}</div>
</div>
<div class="highlight-card student-b">
<div class="highlight-title">总排名</div>
<div class="highlight-value">${totalRankB !== null ? totalRankB : 'N/A'}</div>
<div class="highlight-sub">${selectedStudentB.姓名}</div>
</div>
`;
}
function showHistoricalComparison() {
const resultsDiv = document.getElementById('comparison-results');
// 计算历史平均排名
const subjects = ['语文', '数学', '英语', '物理', '化学', '生物', '历史', '政治', '地理', '总分'];
// 找出学生A和B的优势科目
const advantagesA = [];
const advantagesB = [];
subjects.forEach(subject => {
const ranksA = collectRanks(studentsDataA, subject);
const ranksB = collectRanks(studentsDataB, subject);
if (ranksA.length > 0 && ranksB.length > 0) {
const avgA = (ranksA.reduce((a, b) => a + b, 0) / ranksA.length);
const avgB = (ranksB.reduce((a, b) => a + b, 0) / ranksB.length);
if (avgA < avgB) {
advantagesA.push({subject, diff: avgB - avgA});
} else if (avgB < avgA) {
advantagesB.push({subject, diff: avgA - avgB});
}
}
});
// 按差距排序
advantagesA.sort((a, b) => b.diff - a.diff);
advantagesB.sort((a, b) => b.diff - a.diff);
// 取前三个优势科目
const topAdvantagesA = advantagesA.slice(0, 3).map(a => a.subject).join('、') || '无明显优势科目';
const topAdvantagesB = advantagesB.slice(0, 3).map(a => a.subject).join('、') || '无明显优势科目';
let highlightsHTML = `
<div class="comparison-highlights">
<div class="highlight-card student-a">
<div class="highlight-title">优势科目</div>
<div class="highlight-value">${topAdvantagesA}</div>
<div class="highlight-sub">${selectedStudentA.姓名}</div>
</div>
<div class="highlight-card student-b">
<div class="highlight-title">优势科目</div>
<div class="highlight-value">${topAdvantagesB}</div>
<div class="highlight-sub">${selectedStudentB.姓名}</div>
</div>
<div class="highlight-card">
<div class="highlight-title">考试记录数</div>
<div class="highlight-value">${studentsDataA.length} / ${studentsDataB.length}</div>
<div class="highlight-sub">${selectedStudentA.姓名} / ${selectedStudentB.姓名}</div>
</div>
</div>
`;
let tableHTML = `
<h3>历史所有考试对比分析</h3>
${highlightsHTML}
<table class="comparison-table">
<thead>
<tr>
<th>科目/项目</th>
<th>${selectedStudentA.姓名} (平均排名)</th>
<th>${selectedStudentB.姓名} (平均排名)</th>
<th>对比结果</th>
</tr>
</thead>
<tbody>
`;
subjects.forEach(subject => {
const ranksA = collectRanks(studentsDataA, subject);
const ranksB = collectRanks(studentsDataB, subject);
if (ranksA.length > 0 || ranksB.length > 0) {
const avgA = ranksA.length > 0 ? (ranksA.reduce((a, b) => a + b, 0) / ranksA.length).toFixed(2) : 'N/A';
const avgB = ranksB.length > 0 ? (ranksB.reduce((a, b) => a + b, 0) / ranksB.length).toFixed(2) : 'N/A';
let comparison = '';
let classA = '', classB = '';
let diffIndicator = '';
if (avgA !== 'N/A' && avgB !== 'N/A') {
const numA = parseFloat(avgA);
const numB = parseFloat(avgB);
const diff = Math.abs(numA - numB).toFixed(2);
const maxRank = Math.max(numA, numB) * 1.5;
const diffPercent = Math.min(100, Math.round((diff / maxRank) * 100));
if (numA < numB) {
comparison = `${selectedStudentA.姓名} 平均领先 ${diff} 名`;
classA = 'better-score';
classB = 'worse-score';
diffIndicator = `
<div class="rank-diff-indicator">
<div class="rank-diff-bar better" style="width: ${diffPercent}%"></div>
</div>
`;
} else if (numA > numB) {
comparison = `${selectedStudentB.姓名} 平均领先 ${diff} 名`;
classA = 'worse-score';
classB = 'better-score';
diffIndicator = `
<div class="rank-diff-indicator">
<div class="rank-diff-bar worse" style="width: ${diffPercent}%"></div>
</div>
`;
} else {
comparison = '平均排名相同';
}
}
tableHTML += `
<tr>
<td data-label="科目/项目">${subject}</td>
<td data-label="${selectedStudentA.姓名}" class="${classA}">${avgA} (${ranksA.length}次考试)</td>
<td data-label="${selectedStudentB.姓名}" class="${classB}">${avgB} (${ranksB.length}次考试)</td>
<td data-label="对比结果">
${comparison}
${diffIndicator}
</td>
</tr>
`;
}
});
tableHTML += `
</tbody>
</table>
`;
resultsDiv.innerHTML = tableHTML;
// 更新历史比较的图表
updateBarChart();
updateRadarChart();
}
function collectRanks(examData, subject) {
const ranks = [];
examData.forEach(exam => {
let rankStr;
if (subject === '总分') {
rankStr = exam['大类排名'];
} else {
rankStr = exam[`${subject}校次`];
}
if (rankStr && rankStr !== '无数据' && rankStr.trim() !== '') {
const rankVal = parseInt(rankStr);
if (!isNaN(rankVal)) {
ranks.push(rankVal);
}
}
});
return ranks;
}
// 初始化所有图表
function initCharts() {
initRadarChart();
initBarChart();
initTrendChart();
initSubjectToggles();
}
// 初始化科目选择器
function initSubjectToggles() {
const subjects = ['总分', '语文', '数学', '英语', '物理', '化学', '生物', '历史', '政治', '地理'];
const barToggleContainer = document.getElementById('subject-toggles-bar');
const trendToggleContainer = document.getElementById('subject-toggles-trend');
// 清空现有内容
barToggleContainer.innerHTML = '';
trendToggleContainer.innerHTML = '';
subjects.forEach(subject => {
// 为柱状图创建选择器
const barToggle = document.createElement('div');
barToggle.className = `subject-toggle ${subject === activeSubject ? 'active' : ''}`;
barToggle.textContent = subject;
barToggle.dataset.subject = subject;
barToggle.addEventListener('click', function() {
document.querySelectorAll('#subject-toggles-bar .subject-toggle').forEach(
t => t.classList.remove('active')
);
this.classList.add('active');
activeSubject = this.dataset.subject;
updateBarChart();
});
barToggleContainer.appendChild(barToggle);
// 为趋势图创建选择器
const trendToggle = document.createElement('div');
trendToggle.className = `subject-toggle ${subject === activeSubject ? 'active' : ''}`;
trendToggle.textContent = subject;
trendToggle.dataset.subject = subject;
trendToggle.addEventListener('click', function() {
document.querySelectorAll('#subject-toggles-trend .subject-toggle').forEach(
t => t.classList.remove('active')
);
this.classList.add('active');
activeSubject = this.dataset.subject;
updateTrendChart();
});
trendToggleContainer.appendChild(trendToggle);
});
}
// 初始化雷达图
function initRadarChart() {
const ctx = document.getElementById('radar-chart').getContext('2d');
if (charts.radarChart) {
charts.radarChart.destroy();
}
charts.radarChart = new Chart(ctx, {
type: 'radar',
data: {
labels: ['语文', '数学', '英语', '物理', '化学', '生物', '历史', '政治', '地理'],
datasets: []
},
options: {
scales: {
r: {
angleLines: {
display: true
},
suggestedMin: 0,
suggestedMax: 100,
ticks: {
display: false
},
pointLabels: {
font: {
size: 12,
weight: 'bold'
}
},
reverse: true
}
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.r !== null) {
label += `排名 ${context.parsed.r}`;
}
return label;
}
}
}
}
}
});
updateRadarChart();
}
// 更新雷达图数据
function updateRadarChart(examA, examB) {
if (!charts.radarChart) return;
const subjects = ['语文', '数学', '英语', '物理', '化学', '生物', '历史', '政治', '地理'];
// 如果提供了特定考试数据,使用该考试的数据
// 否则使用所有考试的平均排名
let dataA, dataB;
if (examA && examB) {
dataA = subjects.map(subject => {
const rank = examA[`${subject}校次`];
return rank && rank !== '无数据' ? parseInt(rank) : null;
});
dataB = subjects.map(subject => {
const rank = examB[`${subject}校次`];
return rank && rank !== '无数据' ? parseInt(rank) : null;
});
} else {
dataA = subjects.map(subject => {
const ranks = collectRanks(studentsDataA, subject);
return ranks.length > 0 ?
(ranks.reduce((a, b) => a + b, 0) / ranks.length) : null;
});
dataB = subjects.map(subject => {
const ranks = collectRanks(studentsDataB, subject);
return ranks.length > 0 ?
(ranks.reduce((a, b) => a + b, 0) / ranks.length) : null;
});
}
// 更新图表数据
charts.radarChart.data.datasets = [
{
label: selectedStudentA.姓名,
data: dataA,
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)'
},
{
label: selectedStudentB.姓名,
data: dataB,
backgroundColor: 'rgba(255, 107, 107, 0.3)',
borderColor: 'rgb(255, 107, 107)',
pointBackgroundColor: 'rgb(255, 107, 107)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgb(255, 107, 107)'
}
];
charts.radarChart.update();
}
// 初始化柱状图
function initBarChart() {
const ctx = document.getElementById('bar-chart').getContext('2d');
if (charts.barChart) {
charts.barChart.destroy();
}
charts.barChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['平均排名', '最高排名', '最低排名'],
datasets: []
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
reverse: true, // 排名越小越好
title: {
display: true,
text: '排名'
}
},
x: {
grid: {
display: false
}
}
},
plugins: {
legend: {
position: 'top',
}
},
animation: {
duration: 1000
}
}
});
updateBarChart();
}
// 更新柱状图数据
function updateBarChart() {
if (!charts.barChart) return;
// 获取选中科目的数据
const subject = activeSubject;
// 收集A学生的排名数据
const ranksA = collectRanks(studentsDataA, subject);
const avgA = ranksA.length > 0 ? (ranksA.reduce((a, b) => a + b, 0) / ranksA.length) : null;
const minA = ranksA.length > 0 ? Math.min(...ranksA) : null; // 最高排名
const maxA = ranksA.length > 0 ? Math.max(...ranksA) : null; // 最低排名
// 收集B学生的排名数据
const ranksB = collectRanks(studentsDataB, subject);
const avgB = ranksB.length > 0 ? (ranksB.reduce((a, b) => a + b, 0) / ranksB.length) : null;
const minB = ranksB.length > 0 ? Math.min(...ranksB) : null; // 最高排名
const maxB = ranksB.length > 0 ? Math.max(...ranksB) : null; // 最低排名
// 更新图表数据
charts.barChart.data.datasets = [
{
label: selectedStudentA.姓名,
data: [avgA, minA, maxA],
backgroundColor: 'rgba(67, 97, 238, 0.7)',
borderColor: 'rgb(67, 97, 238)',
borderWidth: 1,
maxBarThickness: 50 // 限制柱状图最大宽度
},
{
label: selectedStudentB.姓名,
data: [avgB, minB, maxB],
backgroundColor: 'rgba(255, 107, 107, 0.7)',
borderColor: 'rgb(255, 107, 107)',
borderWidth: 1,
maxBarThickness: 50 // 限制柱状图最大宽度
}
];
// 更新标题和选项
charts.barChart.options.plugins.title = {
display: true,
text: `${subject} 排名统计对比`,
font: {
size: 14 // 移动端减小字体
}
};
// 移动端优化
if (window.innerWidth <= 768) {
charts.barChart.options.plugins.legend.labels.font = {
size: 12
};
charts.barChart.options.scales.x.ticks.font = {
size: 10
};
charts.barChart.options.scales.y.ticks.font = {
size: 10
};
}
charts.barChart.update();
}
// 初始化趋势图
function initTrendChart() {
const ctx = document.getElementById('trend-chart').getContext('2d');
if (charts.trendChart) {
charts.trendChart.destroy();
}
charts.trendChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: []
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
reverse: true, // 排名越小越好
title: {
display: true,
text: '排名'
}
},
x: {
title: {
display: true,
text: '考试时间'
}
}
},
plugins: {
legend: {
position: 'top',
},
tooltip: {
mode: 'index',
intersect: false,
}
},
elements: {
line: {
tension: 0.3
},
point: {
radius: 5,
hitRadius: 10,
hoverRadius: 7
}
}
}
});
updateTrendChart();
}
// 更新趋势图
function updateTrendChart() {
if (!charts.trendChart) return;
// 获取选中科目
const subject = activeSubject;
// 按时间排序的所有考试
const allExamsA = [...studentsDataA].sort((a, b) =>
(a.sort_index || 0) - (b.sort_index || 0)
);
const allExamsB = [...studentsDataB].sort((a, b) =>
(a.sort_index || 0) - (b.sort_index || 0)
);
// 收集考试名称和日期作为标签,并按 sort_index 排序
const allExams = [...studentsDataA, ...studentsDataB];
const uniqueExamsMap = new Map();
allExams.forEach(exam => {
const name = exam.考试名称 || exam.考试时间;
if (!uniqueExamsMap.has(name)) {
uniqueExamsMap.set(name, exam);
}
});
const sortedUniqueExams = Array.from(uniqueExamsMap.values()).sort((a, b) =>
(a.sort_index || 0) - (b.sort_index || 0)
);
const uniqueLabels = sortedUniqueExams.map(exam => exam.考试名称 || exam.考试时间);
// 收集排名数据
const dataA = uniqueLabels.map(label => {
const exam = allExamsA.find(e => (e.考试名称 || e.考试时间) === label);
if (!exam) return null;
const rankKey = subject === '总分' ? '大类排名' : `${subject}校次`;
const rankStr = exam[rankKey];
return rankStr && rankStr !== '无数据' ? parseInt(rankStr) : null;
});
const dataB = uniqueLabels.map(label => {
const exam = allExamsB.find(e => (e.考试名称 || e.考试时间) === label);
if (!exam) return null;
const rankKey = subject === '总分' ? '大类排名' : `${subject}校次`;
const rankStr = exam[rankKey];
return rankStr && rankStr !== '无数据' ? parseInt(rankStr) : null;
});
// 更新图表数据
charts.trendChart.data.labels = uniqueLabels;
charts.trendChart.data.datasets = [
{
label: selectedStudentA.姓名,
data: dataA,
borderColor: 'rgb(67, 97, 238)',
backgroundColor: 'rgba(67, 97, 238, 0.1)',
fill: false,
pointBackgroundColor: 'rgb(67, 97, 238)'
},
{
label: selectedStudentB.姓名,
data: dataB,
borderColor: 'rgb(255, 107, 107)',
backgroundColor: 'rgba(255, 107, 107, 0.1)',
fill: false,
pointBackgroundColor: 'rgb(255, 107, 107)'
}
];
// 更新标题
charts.trendChart.options.plugins.title = {
display: true,
text: `${subject} 排名趋势对比`,
font: {
size: 16
}
};
charts.trendChart.update();
}
function showError(message) {
const resultsDiv = document.getElementById('comparison-results');
resultsDiv.innerHTML = `<div class="error-message">${message}</div>`;
}
</script>
</body>
</html>