const state = {
age: { page: 1, size: 10, totalPages: 0, total: 0 },
score: { page: 1, size: 10, totalPages: 0, total: 0 },
nopass: { page: 1, size: 10, totalPages: 0, total: 0 },
examAvg: { page: 1, size: 10, totalPages: 0, total: 0 },
timeSize: { page: 1, size: 10, totalPages: 0, total: 0 },
classAvgTime: { page: 1, size: 10, totalPages: 0, total: 0 }
};
const pages = [
{ id: 'age', name: '按年龄区间查询', icon: '👤' },
{ id: 'class-stats', name: '指定班级人数统计', icon: '🏫' },
{ id: 'all-class-stats', name: '所有班级人数统计', icon: '📊' },
{ id: 'score', name: '按分数段查询', icon: '📝' },
{ id: 'no-pass', name: '不合格学生查询', icon: '⚠️' },
{ id: 'exam-avg', name: '班级考试平均分', icon: '📈' },
{ id: 'salary-top', name: '薪资TOP排行', icon: '💰' },
{ id: 'time-size', name: '就业时长查询', icon: '⏱️' },
{ id: 'class-avg-time', name: '班级平均就业时长', icon: '📅' }
];
function renderSidebar() {
const nav = document.getElementById('nav');
nav.innerHTML = pages.map((p, i) =>
`
${p.icon}${p.name}
`
).join('');
nav.querySelectorAll('.nav-item').forEach(item => {
item.addEventListener('click', () => {
nav.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
item.classList.add('active');
showPage(item.dataset.page);
});
});
}
function showPage(id) {
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
const target = document.getElementById('page-' + id);
if (target) target.classList.add('active');
document.getElementById('top-title').textContent =
pages.find(p => p.id === id)?.name || '';
if (id === 'all-class-stats') loadAllClassStats();
if (id === 'salary-top') loadSalaryTop();
if (id === 'exam-avg') loadExamAvg();
if (id === 'time-size') loadTimeSize();
if (id === 'class-avg-time') loadClassAvgTime();
}
function toast(msg, type = 'success') {
const div = document.createElement('div');
div.className = 'toast ' + type;
div.textContent = msg;
document.body.appendChild(div);
setTimeout(() => div.remove(), 2000);
}
function renderPagination(container, s, onChange) {
if (s.totalPages <= 1) {
container.innerHTML = '';
return;
}
let html = `共 ${s.total} 条 / 第 ${s.page}/${s.totalPages} 页`;
html += ``;
const range = [];
const start = Math.max(1, s.page - 2);
const end = Math.min(s.totalPages, start + 4);
for (let i = start; i <= end; i++) range.push(i);
range.forEach(p => {
html += ``;
});
html += ``;
html += `跳至`;
html += ``;
html += `页`;
container.innerHTML = html;
container.querySelectorAll('button').forEach(btn => {
btn.addEventListener('click', () => {
let p = btn.dataset.p ? parseInt(btn.dataset.p) : null;
if (btn.dataset.act === 'prev') p = s.page - 1;
if (btn.dataset.act === 'next') p = s.page + 1;
if (p && p !== s.page) onChange(p);
});
});
const jumpInput = container.querySelector('input[data-act="jumpto"]');
if (jumpInput) {
jumpInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
let p = parseInt(jumpInput.value);
if (!isNaN(p)) {
p = Math.max(1, Math.min(s.totalPages, p));
if (p !== s.page) onChange(p);
}
}
});
}
}
function formatDate(d) {
if (!d) return '-';
return d.toString().slice(0, 10);
}
function createPages() {
const container = document.getElementById('pages-container');
container.innerHTML = `
查询结果
| 学号 | 姓名 | 性别 | 身份证号 |
出生日期 | 民族 | 专业 | 班级 |
入学日期 | 学籍状态 | 学历 |
`;
}
// ============ 各页面加载函数 ============
async function searchAge() {
const min = parseInt(document.getElementById('age-min').value);
const max = parseInt(document.getElementById('age-max').value);
state.age.page = 1;
await loadAge(min, max);
}
async function loadAge(min, max) {
const s = state.age;
s.size = parseInt(document.getElementById('age-size').value) || 10;
document.querySelector('#age-table tbody').innerHTML = '| 加载中... |
';
try {
const res = await API.getStudentsByAgeRange(s.page, s.size, min, max);
if (res.code !== 200) { toast(res.message, 'error'); return; }
s.total = res.total;
s.totalPages = res.total_pages;
const tbody = document.querySelector('#age-table tbody');
if (!res.data || res.data.length === 0) {
tbody.innerHTML = '| 暂无数据 |
';
} else {
tbody.innerHTML = res.data.map(r => `
| ${r.student_id} | ${r.student_name} | ${r.gender} |
${r.id_card} | ${formatDate(r.birthday)} | ${r.ethnicity} |
${r.major} | ${r.class_id} | ${formatDate(r.enrollment_date)} |
${r.student_status} | ${r.education_level} |
`).join('');
}
renderPagination(document.getElementById('age-pagination'), s, (p) => {
s.page = p; loadAge(min, max);
});
} catch (e) {
document.querySelector('#age-table tbody').innerHTML = '| 请求失败,请检查后端服务是否启动 |
';
}
}
async function searchClassInfo() {
const classId = document.getElementById('class-id').value.trim();
if (!classId) { toast('请输入班级编号', 'error'); return; }
const box = document.getElementById('class-stats-result');
box.innerHTML = '加载中...
';
try {
const res = await API.getClassInfo(classId);
if (res.code !== 200) { toast(res.message, 'error'); box.innerHTML = ''; return; }
const d = res.data;
box.innerHTML = `
班级:${d.class_id} - ${d.class_name}
`;
const chart = echarts.init(document.getElementById('class-info-chart'));
chart.setOption({
tooltip: { trigger: 'item' },
legend: { bottom: 10 },
series: [{
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 2 },
label: { show: true, formatter: '{b}: {c} ({d}%)' },
data: [
{ value: d.male_count, name: '男生', itemStyle: { color: '#1890ff' } },
{ value: d.female_count, name: '女生', itemStyle: { color: '#eb2f96' } }
]
}]
});
} catch (e) {
box.innerHTML = '请求失败,请检查后端服务是否启动
';
}
}
async function loadAllClassStats() {
try {
const res = await API.getAllClassStats();
if (res.code !== 200) { toast(res.message, 'error'); return; }
const data = res.data || [];
const tbody = document.querySelector('#all-class-table tbody');
if (!data.length) {
tbody.innerHTML = '| 暂无数据 |
';
} else {
tbody.innerHTML = data.map(r => `
| ${r.class_id} | ${r.class_name} |
${r.total_count} | ${r.male_count} | ${r.female_count} |
`).join('');
}
const chart = echarts.init(document.getElementById('all-class-chart'));
chart.setOption({
tooltip: { trigger: 'axis' },
legend: { data: ['男生', '女生', '总人数'] },
grid: { left: 50, right: 30, top: 50, bottom: 80 },
xAxis: {
type: 'category',
data: data.map(d => d.class_name),
axisLabel: { rotate: 30 }
},
yAxis: { type: 'value' },
series: [
{ name: '男生', type: 'bar', data: data.map(d => d.male_count), itemStyle: { color: '#1890ff' } },
{ name: '女生', type: 'bar', data: data.map(d => d.female_count), itemStyle: { color: '#eb2f96' } },
{ name: '总人数', type: 'bar', data: data.map(d => d.total_count), itemStyle: { color: '#52c41a' } }
]
});
} catch (e) {
console.error(e);
}
}
async function searchScore() {
state.score.page = 1;
await loadScore();
}
async function loadScore() {
const s = state.score;
s.size = parseInt(document.getElementById('score-size').value) || 10;
const score = parseFloat(document.getElementById('score-val').value);
try {
const res = await API.getStudentsByScore(s.page, s.size, score);
if (res.code !== 200) { toast(res.message, 'error'); return; }
s.total = res.total; s.totalPages = res.total_pages;
const tbody = document.querySelector('#score-table tbody');
if (!res.data.length) {
tbody.innerHTML = '| 暂无数据 |
';
} else {
tbody.innerHTML = res.data.map(r =>
`| ${r.student_id} | ${r.student_name} | ${r.score} |
`
).join('');
}
renderPagination(document.getElementById('score-pagination'), s, (p) => {
s.page = p; loadScore();
});
} catch (e) { toast('请求失败', 'error'); }
}
async function searchNoPass() {
state.nopass.page = 1;
await loadNoPass();
}
async function loadNoPass() {
const s = state.nopass;
s.size = parseInt(document.getElementById('nopass-size').value) || 10;
const fc = parseInt(document.getElementById('fail-count').value);
try {
const res = await API.getNoPassStudents(s.page, s.size, fc);
if (res.code !== 200) { toast(res.message, 'error'); return; }
s.total = res.total; s.totalPages = res.total_pages;
const tbody = document.querySelector('#nopass-table tbody');
if (!res.data.length) {
tbody.innerHTML = '| 暂无数据 |
';
} else {
tbody.innerHTML = res.data.map(r =>
`| ${r.student_id} | ${r.student_name} | ${r.fail_count} |
`
).join('');
}
renderPagination(document.getElementById('nopass-pagination'), s, (p) => {
s.page = p; loadNoPass();
});
} catch (e) { toast('请求失败', 'error'); }
}
async function loadExamAvg() {
const s = state.examAvg;
s.size = parseInt(document.getElementById('exam-avg-size').value) || 10;
try {
const res = await API.getClassExamAvg(s.page, s.size);
if (res.code !== 200) { toast(res.message, 'error'); return; }
s.total = res.total; s.totalPages = res.total_pages;
const data = res.data || [];
const tbody = document.querySelector('#exam-avg-table tbody');
if (!data.length) {
tbody.innerHTML = '| 暂无数据 |
';
} else {
tbody.innerHTML = data.map(r =>
`| ${r.course_id} | ${r.class_id} | ${r.class_name} | ${r.avg_score.toFixed(2)} |
`
).join('');
}
const chart = echarts.init(document.getElementById('exam-avg-chart'));
chart.setOption({
tooltip: { trigger: 'axis' },
grid: { left: 50, right: 30, top: 30, bottom: 80 },
xAxis: {
type: 'category',
data: data.map(d => d.class_name + '-' + d.course_id),
axisLabel: { rotate: 40 }
},
yAxis: { type: 'value', min: 0, max: 100 },
series: [{
type: 'bar',
data: data.map(d => d.avg_score.toFixed(2)),
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#2a5298' },
{ offset: 1, color: '#1e3c72' }
])
},
label: { show: true, position: 'top', formatter: '{c}' }
}]
});
renderPagination(document.getElementById('exam-avg-pagination'), s, (p) => {
s.page = p; loadExamAvg();
});
} catch (e) { toast('请求失败', 'error'); }
}
async function loadSalaryTop() {
const m = parseInt(document.getElementById('salary-n').value) || 5;
try {
const res = await API.getSalaryTop(m);
if (res.code !== 200) { toast(res.message, 'error'); return; }
const data = res.data || [];
const tbody = document.querySelector('#salary-table tbody');
if (!data.length) {
tbody.innerHTML = '| 暂无数据 |
';
} else {
tbody.innerHTML = data.map(r =>
`| ${r.student_name} | ${r.class_name} | ${r.company_name} |
${formatDate(r.part_time)} | ${r.salary} |
`
).join('');
}
const chart = echarts.init(document.getElementById('salary-chart'));
chart.setOption({
tooltip: { trigger: 'axis' },
grid: { left: 80, right: 30, top: 30, bottom: 30 },
xAxis: { type: 'value' },
yAxis: { type: 'category', data: data.map(d => d.student_name).reverse() },
series: [{
type: 'bar',
data: data.map(d => d.salary).reverse(),
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#fa709a' },
{ offset: 1, color: '#fee140' }
])
},
label: { show: true, position: 'right', formatter: '{c}' }
}]
});
} catch (e) { toast('请求失败', 'error'); }
}
async function loadTimeSize() {
const s = state.timeSize;
s.size = parseInt(document.getElementById('time-size').value) || 10;
try {
const res = await API.getTimeSize(s.page, s.size);
if (res.code !== 200) { toast(res.message, 'error'); return; }
s.total = res.total; s.totalPages = res.total_pages;
const tbody = document.querySelector('#time-table tbody');
if (!res.data.length) {
tbody.innerHTML = '| 暂无数据 |
';
} else {
tbody.innerHTML = res.data.map(r =>
`| ${r.student_id} | ${r.student_name} |
${formatDate(r.resume_open_date)} |
${formatDate(r.offer_date)} | ${r.time_size} |
`
).join('');
}
renderPagination(document.getElementById('time-pagination'), s, (p) => {
s.page = p; loadTimeSize();
});
} catch (e) { toast('请求失败', 'error'); }
}
async function loadClassAvgTime() {
const s = state.classAvgTime;
s.size = parseInt(document.getElementById('class-avg-time-size').value) || 10;
try {
const res = await API.getClassAvgTimeSize(s.page, s.size);
if (res.code !== 200) { toast(res.message, 'error'); return; }
s.total = res.total; s.totalPages = res.total_pages;
const data = res.data || [];
const tbody = document.querySelector('#class-avg-time-table tbody');
if (!data.length) {
tbody.innerHTML = '| 暂无数据 |
';
} else {
tbody.innerHTML = data.map(r =>
`| ${r.class_id} | ${r.class_name} | ${r.avg_time_size.toFixed(1)} |
`
).join('');
}
const chart = echarts.init(document.getElementById('class-avg-time-chart'));
chart.setOption({
tooltip: { trigger: 'axis' },
grid: { left: 50, right: 30, top: 30, bottom: 80 },
xAxis: {
type: 'category',
data: data.map(d => d.class_name),
axisLabel: { rotate: 30 }
},
yAxis: { type: 'value', name: '天数' },
series: [{
type: 'bar',
data: data.map(d => d.avg_time_size.toFixed(1)),
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#43e97b' },
{ offset: 1, color: '#38f9d7' }
])
},
label: { show: true, position: 'top', formatter: '{c}天' }
}]
});
renderPagination(document.getElementById('class-avg-time-pagination'), s, (p) => {
s.page = p; loadClassAvgTime();
});
} catch (e) { toast('请求失败', 'error'); }
}
function reloadExamAvg() { state.examAvg.page = 1; loadExamAvg(); }
function reloadTimeSize() { state.timeSize.page = 1; loadTimeSize(); }
function reloadClassAvgTime() { state.classAvgTime.page = 1; loadClassAvgTime(); }
// ============ 启动 ============
window.addEventListener('DOMContentLoaded', () => {
createPages();
renderSidebar();
window.searchAge = searchAge;
window.searchClassInfo = searchClassInfo;
window.searchScore = searchScore;
window.searchNoPass = searchNoPass;
window.loadSalaryTop = loadSalaryTop;
window.reloadExamAvg = reloadExamAvg;
window.reloadTimeSize = reloadTimeSize;
window.reloadClassAvgTime = reloadClassAvgTime;
window.addEventListener('resize', () => {
document.querySelectorAll('.chart-container').forEach(el => {
const inst = echarts.getInstanceByDom(el);
if (inst) inst.resize();
});
});
});
// 页面切换时也触发对应数据加载
const origShowPage = showPage;