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) => `` ).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 = `
查询条件
查询结果
学号姓名性别身份证号 出生日期民族专业班级 入学日期学籍状态学历
查询条件
所有班级人数统计
班级编号班级名称总人数男生女生
查询条件
分数 ≥ 指定值的学生
学号姓名分数
查询条件
不合格学生列表
学号姓名不及格门数
每次考试每个班级平均分(从高到低)
课程编号班级编号班级名称平均分
薪资排行 TOP
姓名班级公司入职时间薪资(元/月)
每个学生的就业时长
学号姓名开放简历时间Offer时间就业时长(天)
每个班级的平均就业时长
班级编号班级名称平均就业时长(天)
`; } // ============ 各页面加载函数 ============ 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}
总人数
${d.total_count}
男生
${d.male_count}
女生
${d.female_count}
`; 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;