2138 lines
79 KiB
HTML
2138 lines
79 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>学生管理系统</title>
|
||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
:root {
|
||
--primary: #1E88E5;
|
||
--primary-dark: #1565C0;
|
||
--primary-light: #E3F2FD;
|
||
--bg: #F5F7FA;
|
||
--white: #ffffff;
|
||
--text: #333333;
|
||
--text-secondary: #666666;
|
||
--text-muted: #999999;
|
||
--border: #E0E0E0;
|
||
--success: #4CAF50;
|
||
--warning: #FF9800;
|
||
--danger: #F44336;
|
||
--shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||
--shadow-hover: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||
--radius: 8px;
|
||
--sidebar-width: 240px;
|
||
--header-height: 64px;
|
||
}
|
||
|
||
body {
|
||
font-family: 'PingFang SC', 'Microsoft YaHei', -apple-system, BlinkMacSystemFont, sans-serif;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
min-height: 100vh;
|
||
}
|
||
|
||
/* 侧边栏 */
|
||
.sidebar {
|
||
position: fixed;
|
||
left: 0;
|
||
top: 0;
|
||
width: var(--sidebar-width);
|
||
height: 100vh;
|
||
background: var(--white);
|
||
border-right: 1px solid var(--border);
|
||
padding: 20px 0;
|
||
z-index: 100;
|
||
}
|
||
|
||
.logo {
|
||
padding: 0 24px 24px;
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: var(--primary);
|
||
border-bottom: 1px solid var(--border);
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.nav-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 14px 24px;
|
||
color: var(--text-secondary);
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.nav-item:hover {
|
||
background: var(--primary-light);
|
||
color: var(--primary);
|
||
}
|
||
|
||
.nav-item.active {
|
||
background: var(--primary-light);
|
||
color: var(--primary);
|
||
border-left: 3px solid var(--primary);
|
||
padding-left: 21px;
|
||
}
|
||
|
||
.nav-icon {
|
||
width: 20px;
|
||
height: 20px;
|
||
margin-right: 12px;
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.nav-item.active .nav-icon {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* 主内容区 */
|
||
.main-content {
|
||
margin-left: var(--sidebar-width);
|
||
min-height: 100vh;
|
||
}
|
||
|
||
/* 顶部导航 */
|
||
.header {
|
||
height: var(--header-height);
|
||
background: var(--white);
|
||
border-bottom: 1px solid var(--border);
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 32px;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 50;
|
||
}
|
||
|
||
.breadcrumb {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
color: var(--text-muted);
|
||
font-size: 14px;
|
||
}
|
||
|
||
.breadcrumb .current {
|
||
color: var(--text);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.header-right {
|
||
margin-left: auto;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.user-avatar {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 50%;
|
||
background: var(--primary);
|
||
color: var(--white);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* 页面内容 */
|
||
.page {
|
||
display: none;
|
||
padding: 32px;
|
||
animation: slideIn 0.3s ease;
|
||
}
|
||
|
||
.page.active {
|
||
display: block;
|
||
}
|
||
|
||
@keyframes slideIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateX(20px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateX(0);
|
||
}
|
||
}
|
||
|
||
/* 卡片 */
|
||
.card {
|
||
background: var(--white);
|
||
border-radius: var(--radius);
|
||
box-shadow: var(--shadow);
|
||
padding: 24px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.card:hover {
|
||
box-shadow: var(--shadow-hover);
|
||
}
|
||
|
||
.card-click {
|
||
cursor: pointer;
|
||
transition: transform 0.15s;
|
||
}
|
||
|
||
.card-click:active {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
/* 首页 */
|
||
.welcome-banner {
|
||
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
|
||
color: var(--white);
|
||
padding: 32px;
|
||
border-radius: var(--radius);
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.welcome-banner h1 {
|
||
font-size: 28px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.welcome-banner .streak {
|
||
font-size: 14px;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.quick-actions {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||
gap: 16px;
|
||
margin-bottom: 32px;
|
||
}
|
||
|
||
.quick-card {
|
||
background: var(--white);
|
||
border-radius: var(--radius);
|
||
padding: 24px;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
box-shadow: var(--shadow);
|
||
}
|
||
|
||
.quick-card:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: var(--shadow-hover);
|
||
}
|
||
|
||
.quick-card:active {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.quick-card .icon {
|
||
font-size: 32px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.quick-card .label {
|
||
font-size: 14px;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.news-list {
|
||
background: var(--white);
|
||
border-radius: var(--radius);
|
||
box-shadow: var(--shadow);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.news-header {
|
||
padding: 16px 24px;
|
||
border-bottom: 1px solid var(--border);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.news-item {
|
||
padding: 16px 24px;
|
||
border-bottom: 1px solid var(--border);
|
||
cursor: pointer;
|
||
transition: background 0.2s;
|
||
}
|
||
|
||
.news-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.news-item:hover {
|
||
background: var(--primary-light);
|
||
}
|
||
|
||
.news-item .time {
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.news-item .title {
|
||
font-size: 14px;
|
||
color: var(--text);
|
||
}
|
||
|
||
/* 表格 */
|
||
.table-container {
|
||
background: var(--white);
|
||
border-radius: var(--radius);
|
||
box-shadow: var(--shadow);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.table-header {
|
||
padding: 20px 24px;
|
||
border-bottom: 1px solid var(--border);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.table-title {
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
th, td {
|
||
padding: 14px 24px;
|
||
text-align: left;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
th {
|
||
background: var(--bg);
|
||
font-weight: 500;
|
||
color: var(--text-secondary);
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
|
||
th:hover {
|
||
background: #E8EBF0;
|
||
}
|
||
|
||
tr:nth-child(even) {
|
||
background: #FAFBFC;
|
||
}
|
||
|
||
tr:hover {
|
||
background: var(--primary-light);
|
||
}
|
||
|
||
/* 按钮 */
|
||
.btn {
|
||
padding: 8px 20px;
|
||
border-radius: 6px;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
transition: all 0.2s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: var(--primary);
|
||
color: var(--white);
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: var(--primary-dark);
|
||
}
|
||
|
||
.btn-outline {
|
||
background: transparent;
|
||
color: var(--primary);
|
||
border: 1px solid var(--primary);
|
||
}
|
||
|
||
.btn-outline:hover {
|
||
background: var(--primary-light);
|
||
}
|
||
|
||
.btn-danger {
|
||
background: var(--danger);
|
||
color: var(--white);
|
||
}
|
||
|
||
.btn-danger:hover {
|
||
background: #D32F2F;
|
||
}
|
||
|
||
.btn-sm {
|
||
padding: 6px 12px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* 表单 */
|
||
.form-group {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.form-label {
|
||
display: block;
|
||
margin-bottom: 6px;
|
||
font-size: 14px;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.form-input {
|
||
width: 100%;
|
||
padding: 10px 14px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
transition: border-color 0.2s;
|
||
}
|
||
|
||
.form-input:focus {
|
||
outline: none;
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
.form-row {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 16px;
|
||
}
|
||
|
||
/* 模态框 */
|
||
.modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.modal-overlay.active {
|
||
opacity: 1;
|
||
visibility: visible;
|
||
}
|
||
|
||
.modal {
|
||
background: var(--white);
|
||
border-radius: var(--radius);
|
||
padding: 24px;
|
||
max-width: 500px;
|
||
width: 90%;
|
||
max-height: 90vh;
|
||
overflow-y: auto;
|
||
transform: scale(0.9);
|
||
transition: transform 0.3s;
|
||
}
|
||
|
||
.modal-overlay.active .modal {
|
||
transform: scale(1);
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.modal-title {
|
||
font-size: 18px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.modal-close {
|
||
background: none;
|
||
border: none;
|
||
font-size: 24px;
|
||
cursor: pointer;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.modal-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 12px;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
/* Toast */
|
||
.toast-container {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
z-index: 2000;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.toast {
|
||
background: var(--white);
|
||
border-radius: 6px;
|
||
padding: 14px 20px;
|
||
box-shadow: var(--shadow-hover);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
animation: toastIn 0.3s ease;
|
||
min-width: 280px;
|
||
}
|
||
|
||
.toast.success {
|
||
border-left: 4px solid var(--success);
|
||
}
|
||
|
||
.toast.error {
|
||
border-left: 4px solid var(--danger);
|
||
}
|
||
|
||
.toast.info {
|
||
border-left: 4px solid var(--primary);
|
||
}
|
||
|
||
@keyframes toastIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateX(100%);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateX(0);
|
||
}
|
||
}
|
||
|
||
/* 标签 */
|
||
.tag {
|
||
display: inline-block;
|
||
padding: 4px 10px;
|
||
border-radius: 12px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.tag-success {
|
||
background: #E8F5E9;
|
||
color: var(--success);
|
||
}
|
||
|
||
.tag-warning {
|
||
background: #FFF3E0;
|
||
color: var(--warning);
|
||
}
|
||
|
||
.tag-danger {
|
||
background: #FFEBEE;
|
||
color: var(--danger);
|
||
}
|
||
|
||
.tag-primary {
|
||
background: var(--primary-light);
|
||
color: var(--primary);
|
||
}
|
||
|
||
/* 搜索栏 */
|
||
.search-bar {
|
||
display: flex;
|
||
gap: 12px;
|
||
margin-bottom: 20px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.search-input {
|
||
flex: 1;
|
||
min-width: 200px;
|
||
padding: 10px 14px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.search-input:focus {
|
||
outline: none;
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
/* 分页 */
|
||
.pagination {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 20px;
|
||
}
|
||
|
||
.page-btn {
|
||
padding: 8px 14px;
|
||
border: 1px solid var(--border);
|
||
background: var(--white);
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.page-btn:hover {
|
||
background: var(--primary-light);
|
||
border-color: var(--primary);
|
||
color: var(--primary);
|
||
}
|
||
|
||
.page-btn.active {
|
||
background: var(--primary);
|
||
border-color: var(--primary);
|
||
color: var(--white);
|
||
}
|
||
|
||
/* 成绩卡片 */
|
||
.score-cards {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||
gap: 16px;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.score-card {
|
||
background: var(--white);
|
||
border-radius: var(--radius);
|
||
padding: 20px;
|
||
box-shadow: var(--shadow);
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.score-card:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: var(--shadow-hover);
|
||
}
|
||
|
||
.score-card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.score-card-title {
|
||
font-weight: 500;
|
||
}
|
||
|
||
.score-card-value {
|
||
font-size: 24px;
|
||
font-weight: 600;
|
||
color: var(--primary);
|
||
}
|
||
|
||
.score-card-value.pass {
|
||
color: var(--success);
|
||
}
|
||
|
||
.score-card-value.fail {
|
||
color: var(--danger);
|
||
}
|
||
|
||
/* 图表容器 */
|
||
.chart-container {
|
||
background: var(--white);
|
||
border-radius: var(--radius);
|
||
padding: 24px;
|
||
box-shadow: var(--shadow);
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.chart-title {
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
/* 统计卡片 */
|
||
.stats-overview {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||
gap: 20px;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.stat-card {
|
||
background: var(--white);
|
||
border-radius: var(--radius);
|
||
padding: 20px;
|
||
box-shadow: var(--shadow);
|
||
transition: all 0.2s;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.stat-card:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: var(--shadow-hover);
|
||
}
|
||
|
||
.stat-card-primary { border-left: 4px solid var(--primary); }
|
||
.stat-card-success { border-left: 4px solid var(--success); }
|
||
.stat-card-warning { border-left: 4px solid var(--warning); }
|
||
.stat-card-info { border-left: 4px solid var(--info); }
|
||
|
||
.stat-icon {
|
||
font-size: 32px;
|
||
}
|
||
|
||
.stat-content h3 {
|
||
margin: 0 0 4px 0;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.stat-content p {
|
||
margin: 0;
|
||
font-size: 13px;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.stat-content select {
|
||
margin-top: 8px;
|
||
padding: 8px 12px;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-sm);
|
||
background: var(--white);
|
||
color: var(--text-primary);
|
||
font-size: 14px;
|
||
width: 100%;
|
||
}
|
||
|
||
.stat-data {
|
||
margin-top: 8px;
|
||
padding: 12px;
|
||
background: var(--bg-secondary);
|
||
border-radius: var(--radius-sm);
|
||
font-size: 13px;
|
||
color: var(--text-secondary);
|
||
max-height: 150px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.stat-data div {
|
||
padding: 4px 0;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.stat-data div:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
/* 个人中心 */
|
||
.profile-header {
|
||
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
|
||
color: var(--white);
|
||
padding: 40px 32px;
|
||
border-radius: var(--radius);
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.profile-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 24px;
|
||
}
|
||
|
||
.profile-avatar {
|
||
width: 80px;
|
||
height: 80px;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 32px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.profile-details h2 {
|
||
font-size: 24px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.profile-details p {
|
||
opacity: 0.9;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.profile-menu {
|
||
background: var(--white);
|
||
border-radius: var(--radius);
|
||
box-shadow: var(--shadow);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.profile-menu-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 16px 24px;
|
||
border-bottom: 1px solid var(--border);
|
||
cursor: pointer;
|
||
transition: background 0.2s;
|
||
}
|
||
|
||
.profile-menu-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.profile-menu-item:hover {
|
||
background: var(--primary-light);
|
||
}
|
||
|
||
.profile-menu-item .icon {
|
||
width: 24px;
|
||
height: 24px;
|
||
margin-right: 16px;
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.profile-menu-item .label {
|
||
flex: 1;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.profile-menu-item .arrow {
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* 响应式 */
|
||
@media (max-width: 768px) {
|
||
.sidebar {
|
||
width: 60px;
|
||
}
|
||
|
||
.sidebar .logo,
|
||
.sidebar .nav-text {
|
||
display: none;
|
||
}
|
||
|
||
.nav-item {
|
||
justify-content: center;
|
||
padding: 14px 0;
|
||
}
|
||
|
||
.nav-icon {
|
||
margin-right: 0;
|
||
}
|
||
|
||
.main-content {
|
||
margin-left: 60px;
|
||
}
|
||
|
||
.page {
|
||
padding: 16px;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- 侧边栏 -->
|
||
<aside class="sidebar">
|
||
<div class="logo">📚 学生管理系统</div>
|
||
<nav>
|
||
<a class="nav-item active" data-page="home">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"></path>
|
||
<polyline points="9 22 9 12 15 12 15 22"></polyline>
|
||
</svg>
|
||
<span class="nav-text">首页</span>
|
||
</a>
|
||
<a class="nav-item" data-page="students">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"></path>
|
||
<circle cx="9" cy="7" r="4"></circle>
|
||
<path d="M23 21v-2a4 4 0 00-4-4H-3"></path>
|
||
<circle cx="17" cy="7" r="4"></circle>
|
||
</svg>
|
||
<span class="nav-text">学生管理</span>
|
||
</a>
|
||
<a class="nav-item" data-page="scores">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M9 11l3 3L22 4"></path>
|
||
<path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"></path>
|
||
</svg>
|
||
<span class="nav-text">成绩查询</span>
|
||
</a>
|
||
<a class="nav-item" data-page="teachers">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"></path>
|
||
<circle cx="12" cy="7" r="4"></circle>
|
||
</svg>
|
||
<span class="nav-text">教师管理</span>
|
||
</a>
|
||
<a class="nav-item" data-page="employment">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<rect x="2" y="7" width="20" height="14" rx="2" ry="2"></rect>
|
||
<path d="M16 21V5a2 2 0 00-2-2h-4a2 2 0 00-2 2v16"></path>
|
||
</svg>
|
||
<span class="nav-text">就业管理</span>
|
||
</a>
|
||
<a class="nav-item" data-page="stats">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<line x1="18" y1="20" x2="18" y2="10"></line>
|
||
<line x1="12" y1="20" x2="12" y2="4"></line>
|
||
<line x1="6" y1="20" x2="6" y2="14"></line>
|
||
</svg>
|
||
<span class="nav-text">统计分析</span>
|
||
</a>
|
||
<a class="nav-item" data-page="profile">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<circle cx="12" cy="12" r="10"></circle>
|
||
<path d="M12 16v-4"></path>
|
||
<path d="M12 8h.01"></path>
|
||
</svg>
|
||
<span class="nav-text">个人中心</span>
|
||
</a>
|
||
</nav>
|
||
</aside>
|
||
|
||
<!-- 主内容区 -->
|
||
<main class="main-content">
|
||
<!-- 顶部导航 -->
|
||
<header class="header">
|
||
<div class="breadcrumb">
|
||
<span>首页</span>
|
||
<span>/</span>
|
||
<span class="current" id="breadcrumb-current">控制台</span>
|
||
</div>
|
||
<div class="header-right">
|
||
<div class="user-avatar">学</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- 首页 -->
|
||
<div class="page active" id="page-home">
|
||
<div class="welcome-banner">
|
||
<h1>Hi,同学 👋</h1>
|
||
<p class="streak">已连续学习 <strong id="streak-days">3</strong> 天 | 今日是第 3 个学习日</p>
|
||
</div>
|
||
|
||
<div class="quick-actions">
|
||
<div class="quick-card card-click" onclick="navigateTo('scores')">
|
||
<div class="icon">📊</div>
|
||
<div class="label">查课表</div>
|
||
</div>
|
||
<div class="quick-card card-click" onclick="navigateTo('scores')">
|
||
<div class="icon">📝</div>
|
||
<div class="label">查成绩</div>
|
||
</div>
|
||
<div class="quick-card card-click" onclick="navigateTo('employment')">
|
||
<div class="icon">💳</div>
|
||
<div class="label">校园卡</div>
|
||
</div>
|
||
<div class="quick-card card-click" onclick="showToast('报修功能开发中', 'info')">
|
||
<div class="icon">🔧</div>
|
||
<div class="label">报修</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="news-list">
|
||
<div class="news-header">📢 校园动态</div>
|
||
<div class="news-item">
|
||
<div class="time">2026-09-22 10:00</div>
|
||
<div class="title">【教务处】2026年秋季学期期末考试安排通知</div>
|
||
</div>
|
||
<div class="news-item">
|
||
<div class="time">2026-09-21 15:30</div>
|
||
<div class="title">【就业中心】秋季招聘会即将举行,欢迎投递简历</div>
|
||
</div>
|
||
<div class="news-item">
|
||
<div class="time">2026-09-20 09:00</div>
|
||
<div class="title">【图书馆】延长开放时间至24:00</div>
|
||
</div>
|
||
<div class="news-item">
|
||
<div class="time">2026-09-19 14:00</div>
|
||
<div class="title">【学术报告】人工智能前沿技术讲座</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 学生管理 -->
|
||
<div class="page" id="page-students">
|
||
<div class="table-container">
|
||
<div class="table-header">
|
||
<span class="table-title">学生列表</span>
|
||
<button class="btn btn-primary" onclick="openModal('student-modal')">+ 新增学生</button>
|
||
</div>
|
||
<div class="search-bar">
|
||
<input type="text" class="search-input" id="student-search" placeholder="搜索学生姓名、学号..." onkeyup="searchStudents()">
|
||
<select class="search-input" style="flex: 0 0 150px" id="student-class-filter" onchange="searchStudents()">
|
||
<option value="">全部班级</option>
|
||
<option value="1">计算机科学2401班</option>
|
||
<option value="2">计算机科学2402班</option>
|
||
<option value="3">人工智能2501班</option>
|
||
</select>
|
||
</div>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th onclick="sortTable('students-table', 0)">学号 ↕</th>
|
||
<th onclick="sortTable('students-table', 1)">姓名 ↕</th>
|
||
<th onclick="sortTable('students-table', 2)">性别 ↕</th>
|
||
<th onclick="sortTable('students-table', 3)">班级 ↕</th>
|
||
<th onclick="sortTable('students-table', 4)">专业 ↕</th>
|
||
<th onclick="sortTable('students-table', 5)">手机号 ↕</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="students-tbody">
|
||
<!-- 动态生成 -->
|
||
</tbody>
|
||
</table>
|
||
<div class="pagination">
|
||
<button class="page-btn" onclick="changePage('students', -1)">上一页</button>
|
||
<span id="students-page-info">第 1 页 / 共 1 页</span>
|
||
<button class="page-btn" onclick="changePage('students', 1)">下一页</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 成绩查询 -->
|
||
<div class="page" id="page-scores">
|
||
<div class="search-bar">
|
||
<select class="search-input" style="flex: 0 0 150px" id="score-student-filter" onchange="loadScores()">
|
||
<option value="">选择学生</option>
|
||
</select>
|
||
<select class="search-input" style="flex: 0 0 150px" id="score-term-filter" onchange="loadScores()">
|
||
<option value="1">第一学期</option>
|
||
<option value="2">第二学期</option>
|
||
</select>
|
||
<button class="btn btn-primary" onclick="loadScores()">查询</button>
|
||
</div>
|
||
|
||
<div class="score-cards" id="score-cards">
|
||
<!-- 动态生成 -->
|
||
</div>
|
||
|
||
<div class="chart-container">
|
||
<div class="chart-title">成绩趋势图</div>
|
||
<canvas id="score-chart" height="100"></canvas>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 教师管理 -->
|
||
<div class="page" id="page-teachers">
|
||
<div class="table-container">
|
||
<div class="table-header">
|
||
<span class="table-title">教师列表</span>
|
||
<button class="btn btn-primary" onclick="openModal('teacher-modal')">+ 新增教师</button>
|
||
</div>
|
||
<div class="search-bar">
|
||
<input type="text" class="search-input" id="teacher-search" placeholder="搜索教师姓名..." onkeyup="searchTeachers()">
|
||
</div>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>教师ID</th>
|
||
<th>姓名</th>
|
||
<th>性别</th>
|
||
<th>擅长领域</th>
|
||
<th>学历</th>
|
||
<th>毕业院校</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="teachers-tbody">
|
||
<!-- 动态生成 -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 就业管理 -->
|
||
<div class="page" id="page-employment">
|
||
<div class="table-container">
|
||
<div class="table-header">
|
||
<span class="table-title">就业信息</span>
|
||
<button class="btn btn-primary" onclick="openModal('employment-modal')">+ 新增就业信息</button>
|
||
</div>
|
||
<div class="search-bar">
|
||
<input type="text" class="search-input" id="employment-search" placeholder="搜索学生姓名或公司..." onkeyup="searchEmployment()">
|
||
</div>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>学生姓名</th>
|
||
<th>班级</th>
|
||
<th>公司</th>
|
||
<th>薪资</th>
|
||
<th>offer下发时间</th>
|
||
<th>状态</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="employment-tbody">
|
||
<!-- 动态生成 -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 统计分析 -->
|
||
<div class="page" id="page-stats">
|
||
<h2 class="page-title">统计分析</h2>
|
||
|
||
<div class="stats-overview">
|
||
<div class="stat-card stat-card-primary">
|
||
<div class="stat-icon">👥</div>
|
||
<div class="stat-content">
|
||
<h3>班级人数统计</h3>
|
||
<select id="stat-class-type" onchange="loadClassStats()">
|
||
<option value="班级人数">按班级统计</option>
|
||
<option value="男女人数">按性别统计</option>
|
||
<option value="班级中男女人数">班级性别分布</option>
|
||
</select>
|
||
</div>
|
||
<div id="class-stats-result" class="stat-data"></div>
|
||
</div>
|
||
|
||
<div class="stat-card stat-card-success">
|
||
<div class="stat-icon">📊</div>
|
||
<div class="stat-content">
|
||
<h3>优秀成绩学生</h3>
|
||
<p>所有考试都在80分以上</p>
|
||
</div>
|
||
<button class="btn btn-success" onclick="loadExcellentStats()">查询</button>
|
||
<div id="excellent-stats-result" class="stat-data"></div>
|
||
</div>
|
||
|
||
<div class="stat-card stat-card-warning">
|
||
<div class="stat-icon">⚠️</div>
|
||
<div class="stat-content">
|
||
<h3>不及格学生查询</h3>
|
||
<p>两次以上不及格</p>
|
||
</div>
|
||
<button class="btn btn-warning" onclick="loadFailStats()">查询</button>
|
||
<div id="fail-stats-result" class="stat-data"></div>
|
||
</div>
|
||
|
||
<div class="stat-card stat-card-info">
|
||
<div class="stat-icon">📈</div>
|
||
<div class="stat-content">
|
||
<h3>班级平均分</h3>
|
||
<p>每次考试各班级平均分</p>
|
||
</div>
|
||
<button class="btn btn-info" onclick="loadAvgStats()">查询</button>
|
||
<div id="avg-stats-result" class="stat-data"></div>
|
||
</div>
|
||
|
||
<div class="stat-card stat-card-primary">
|
||
<div class="stat-icon">🎓</div>
|
||
<div class="stat-content">
|
||
<h3>就业薪资Top5</h3>
|
||
<p>薪资最高的前5名学生</p>
|
||
</div>
|
||
<button class="btn btn-primary" onclick="loadTopStats()">查询</button>
|
||
<div id="top-stats-result" class="stat-data"></div>
|
||
</div>
|
||
|
||
<div class="stat-card stat-card-success">
|
||
<div class="stat-icon">💼</div>
|
||
<div class="stat-content">
|
||
<h3>就业时长统计</h3>
|
||
<p>每位学生的就业时长</p>
|
||
</div>
|
||
<button class="btn btn-success" onclick="loadJobStats()">查询</button>
|
||
<div id="job-stats-result" class="stat-data"></div>
|
||
</div>
|
||
|
||
<div class="stat-card stat-card-warning">
|
||
<div class="stat-icon">⏱️</div>
|
||
<div class="stat-content">
|
||
<h3>平均就业时长</h3>
|
||
<p>所有就业学生的平均时长</p>
|
||
</div>
|
||
<button class="btn btn-warning" onclick="loadAvgJobStats()">查询</button>
|
||
<div id="avgjob-stats-result" class="stat-data"></div>
|
||
</div>
|
||
|
||
<div class="stat-card stat-card-info">
|
||
<div class="stat-icon">👴</div>
|
||
<div class="stat-content">
|
||
<h3>30岁以上学员</h3>
|
||
<p>年龄超过30岁的学生</p>
|
||
</div>
|
||
<button class="btn btn-info" onclick="loadAgeStats()">查询</button>
|
||
<div id="age-stats-result" class="stat-data"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 个人中心 -->
|
||
<div class="page" id="page-profile">
|
||
<div class="profile-header">
|
||
<div class="profile-info">
|
||
<div class="profile-avatar">学</div>
|
||
<div class="profile-details">
|
||
<h2>张同学</h2>
|
||
<p>学号:20241001 | 计算机科学2401班</p>
|
||
<p>联系方式:13900001001</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="profile-menu">
|
||
<div class="profile-menu-item" onclick="showToast('个人信息页面开发中', 'info')">
|
||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"></path>
|
||
<circle cx="12" cy="7" r="4"></circle>
|
||
</svg>
|
||
<span class="label">个人信息</span>
|
||
<span class="arrow">›</span>
|
||
</div>
|
||
<div class="profile-menu-item" onclick="showToast('设置页面开发中', 'info')">
|
||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<circle cx="12" cy="12" r="3"></circle>
|
||
<path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z"></path>
|
||
</svg>
|
||
<span class="label">设置</span>
|
||
<span class="arrow">›</span>
|
||
</div>
|
||
<div class="profile-menu-item" onclick="showToast('帮助中心开发中', 'info')">
|
||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<circle cx="12" cy="12" r="10"></circle>
|
||
<path d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3"></path>
|
||
<line x1="12" y1="17" x2="12.01" y2="17"></line>
|
||
</svg>
|
||
<span class="label">帮助</span>
|
||
<span class="arrow">›</span>
|
||
</div>
|
||
<div class="profile-menu-item" onclick="showConfirm('确定要退出登录吗?', () => { showToast('已退出登录', 'success'); })">
|
||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"></path>
|
||
<polyline points="16 17 21 12 16 7"></polyline>
|
||
<line x1="21" y1="12" x2="9" y2="12"></line>
|
||
</svg>
|
||
<span class="label">退出登录</span>
|
||
<span class="arrow">›</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- 学生模态框 -->
|
||
<div class="modal-overlay" id="student-modal">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title" id="student-modal-title">新增学生</h3>
|
||
<button class="modal-close" onclick="closeModal('student-modal')">×</button>
|
||
</div>
|
||
<form id="student-form">
|
||
<input type="hidden" id="student-edit-id">
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">姓名 *</label>
|
||
<input type="text" class="form-input" id="student-name" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">性别 *</label>
|
||
<select class="form-input" id="student-sex" required>
|
||
<option value="男">男</option>
|
||
<option value="女">女</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">年龄 *</label>
|
||
<input type="number" class="form-input" id="student-age" min="16" max="60" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">手机号 *</label>
|
||
<input type="text" class="form-input" id="student-phone" maxlength="11" required>
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">班级 *</label>
|
||
<select class="form-input" id="student-class" required>
|
||
<option value="1">计算机科学2401班</option>
|
||
<option value="2">计算机科学2402班</option>
|
||
<option value="3">人工智能2501班</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">专业</label>
|
||
<select class="form-input" id="student-major">
|
||
<option value="计算机科学">计算机科学</option>
|
||
<option value="软件工程">软件工程</option>
|
||
<option value="数据科学">数据科学</option>
|
||
<option value="人工智能">人工智能</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">学历 *</label>
|
||
<select class="form-input" id="student-education" required>
|
||
<option value="本科">本科</option>
|
||
<option value="专科">专科</option>
|
||
<option value="硕士">硕士</option>
|
||
<option value="博士">博士</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">籍贯</label>
|
||
<input type="text" class="form-input" id="student-home">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">毕业院校</label>
|
||
<input type="text" class="form-input" id="student-college">
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-outline" onclick="closeModal('student-modal')">取消</button>
|
||
<button type="submit" class="btn btn-primary">保存</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 教师模态框 -->
|
||
<div class="modal-overlay" id="teacher-modal">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title">新增教师</h3>
|
||
<button class="modal-close" onclick="closeModal('teacher-modal')">×</button>
|
||
</div>
|
||
<form id="teacher-form">
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">姓名 *</label>
|
||
<input type="text" class="form-input" id="teacher-name" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">性别</label>
|
||
<select class="form-input" id="teacher-sex">
|
||
<option value="男">男</option>
|
||
<option value="女">女</option>
|
||
<option value="未知">未知</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">手机号 *</label>
|
||
<input type="text" class="form-input" id="teacher-phone" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">班级ID *</label>
|
||
<input type="number" class="form-input" id="teacher-class-id" required>
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">科目ID *</label>
|
||
<input type="number" class="form-input" id="teacher-subject-id" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">专业方向</label>
|
||
<input type="text" class="form-input" id="teacher-specialty">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">学历</label>
|
||
<select class="form-input" id="teacher-education">
|
||
<option value="博士">博士</option>
|
||
<option value="硕士">硕士</option>
|
||
<option value="本科">本科</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">毕业院校</label>
|
||
<input type="text" class="form-input" id="teacher-collage">
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-outline" onclick="closeModal('teacher-modal')">取消</button>
|
||
<button type="submit" class="btn btn-primary">保存</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 就业模态框 -->
|
||
<div class="modal-overlay" id="employment-modal">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title">新增就业信息</h3>
|
||
<button class="modal-close" onclick="closeModal('employment-modal')">×</button>
|
||
</div>
|
||
<form id="employment-form">
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">学生ID *</label>
|
||
<input type="number" class="form-input" id="emp-student-id" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">班级ID *</label>
|
||
<input type="number" class="form-input" id="emp-class-id" required>
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">公司ID</label>
|
||
<input type="number" class="form-input" id="emp-company-id">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">薪资</label>
|
||
<input type="number" class="form-input" id="emp-salary">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">就业开放时间</label>
|
||
<input type="date" class="form-input" id="emp-open-time">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">offer下发时间</label>
|
||
<input type="date" class="form-input" id="emp-offer-time">
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-outline" onclick="closeModal('employment-modal')">取消</button>
|
||
<button type="submit" class="btn btn-primary">保存</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 确认对话框 -->
|
||
<div class="modal-overlay" id="confirm-modal">
|
||
<div class="modal" style="max-width: 400px;">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title">确认操作</h3>
|
||
</div>
|
||
<p id="confirm-message" style="margin: 20px 0; color: var(--text-secondary);"></p>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-outline" onclick="closeModal('confirm-modal')">取消</button>
|
||
<button class="btn btn-danger" id="confirm-btn">确认</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Toast 容器 -->
|
||
<div class="toast-container" id="toast-container"></div>
|
||
|
||
<script>
|
||
// API 基础地址
|
||
const API_BASE = 'http://localhost:58000';
|
||
|
||
// 全局状态
|
||
let currentPage = 'home';
|
||
let sortState = {};
|
||
let studentData = [];
|
||
let teacherData = [];
|
||
let employmentData = [];
|
||
let scoreData = {};
|
||
|
||
// 页面加载
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
loadStudents();
|
||
loadTeachers();
|
||
loadEmployment();
|
||
initNavigation();
|
||
});
|
||
|
||
// 初始化导航
|
||
function initNavigation() {
|
||
document.querySelectorAll('.nav-item').forEach(item => {
|
||
item.addEventListener('click', (e) => {
|
||
const page = e.currentTarget.dataset.page;
|
||
if (page) navigateTo(page);
|
||
});
|
||
});
|
||
}
|
||
|
||
// 页面导航
|
||
function navigateTo(page) {
|
||
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
||
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
||
|
||
document.getElementById(`page-${page}`).classList.add('active');
|
||
document.querySelector(`.nav-item[data-page="${page}"]`).classList.add('active');
|
||
|
||
currentPage = page;
|
||
document.getElementById('breadcrumb-current').textContent = getPageName(page);
|
||
|
||
if (page === 'scores') {
|
||
loadScores();
|
||
}
|
||
}
|
||
|
||
// 获取页面名称
|
||
function getPageName(page) {
|
||
const names = {
|
||
'home': '控制台',
|
||
'students': '学生管理',
|
||
'scores': '成绩查询',
|
||
'teachers': '教师管理',
|
||
'employment': '就业管理',
|
||
'stats': '统计分析',
|
||
'profile': '个人中心'
|
||
};
|
||
return names[page] || page;
|
||
}
|
||
|
||
// ── 学生管理 ──────────────────────────────────────
|
||
async function loadStudents() {
|
||
try {
|
||
console.log('[DEBUG] 开始加载学生...');
|
||
const res = await fetch(`${API_BASE}/students`);
|
||
console.log('[DEBUG] 响应状态:', res.status);
|
||
if (res.ok) {
|
||
const resp = await res.json();
|
||
console.log('[DEBUG] 响应数据类型:', Array.isArray(resp) ? 'array' : 'object');
|
||
studentData = Array.isArray(resp) ? resp : (resp.data || []);
|
||
console.log('[DEBUG] 学生数据条数:', studentData.length);
|
||
renderStudents(studentData);
|
||
updateStudentSelect();
|
||
} else {
|
||
console.error('[DEBUG] 请求失败:', res.status, res.statusText);
|
||
}
|
||
} catch (err) {
|
||
console.error('加载学生失败:', err);
|
||
showToast('加载学生数据失败', 'error');
|
||
}
|
||
}
|
||
|
||
function renderStudents(data) {
|
||
const tbody = document.getElementById('students-tbody');
|
||
tbody.innerHTML = data.map(s => `
|
||
<tr>
|
||
<td>${s.num}</td>
|
||
<td>${s.name}</td>
|
||
<td><span class="tag ${s.sex === '男' ? 'tag-primary' : s.sex === '女' ? 'tag-warning' : ''}">${s.sex || '-'}</span></td>
|
||
<td>班级${s.class_id}</td>
|
||
<td>${s.specialty || '-'}</td>
|
||
<td>${s.phone}</td>
|
||
<td>
|
||
<button class="btn btn-sm btn-outline" onclick="editStudent(${s.id})">编辑</button>
|
||
<button class="btn btn-sm btn-danger" onclick="deleteStudent(${s.id}, '${s.name}')">删除</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function searchStudents() {
|
||
const keyword = document.getElementById('student-search').value.toLowerCase();
|
||
const classFilter = document.getElementById('student-class-filter').value;
|
||
|
||
let filtered = studentData.filter(s => {
|
||
const matchKeyword = !keyword || s.name.toLowerCase().includes(keyword) || s.num.includes(keyword);
|
||
const matchClass = !classFilter || s.class_id === parseInt(classFilter);
|
||
return matchKeyword && matchClass;
|
||
});
|
||
|
||
renderStudents(filtered);
|
||
}
|
||
|
||
function editStudent(id) {
|
||
const student = studentData.find(s => s.id === id);
|
||
if (!student) return;
|
||
|
||
document.getElementById('student-modal-title').textContent = '编辑学生';
|
||
document.getElementById('student-edit-id').value = id;
|
||
document.getElementById('student-name').value = student.name;
|
||
document.getElementById('student-sex').value = student.sex;
|
||
document.getElementById('student-age').value = student.age;
|
||
document.getElementById('student-phone').value = student.phone;
|
||
document.getElementById('student-class').value = student.class_id;
|
||
document.getElementById('student-major').value = student.specialty || '';
|
||
document.getElementById('student-home').value = student.home_Place || '';
|
||
document.getElementById('student-college').value = student.college || '';
|
||
// 回填学历(后端存储的是中文,下拉框也是中文)
|
||
const eduSel = document.getElementById('student-education');
|
||
if (student.education) {
|
||
eduSel.value = student.education;
|
||
}
|
||
|
||
openModal('student-modal');
|
||
}
|
||
|
||
document.getElementById('student-form').addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
|
||
const id = document.getElementById('student-edit-id').value;
|
||
const data = {
|
||
name: document.getElementById('student-name').value,
|
||
sex: document.getElementById('student-sex').value,
|
||
age: parseInt(document.getElementById('student-age').value),
|
||
phone: document.getElementById('student-phone').value,
|
||
class_id: parseInt(document.getElementById('student-class').value),
|
||
specialty: document.getElementById('student-major').value,
|
||
education: document.getElementById('student-education').value,
|
||
// 默认 advisor_id=1,绕过后端 dao 查 advisor 的 bug
|
||
advisor_id: 1,
|
||
home_Place: document.getElementById('student-home').value,
|
||
college: document.getElementById('student-college').value,
|
||
};
|
||
|
||
if (id) {
|
||
data.id = parseInt(id);
|
||
await updateStudent(data);
|
||
} else {
|
||
// 根据当前最大id自动生成学号
|
||
const maxId = studentData.reduce((max, s) => Math.max(max, s.id || 0), 0);
|
||
data.num = '2024' + String(maxId + 1001).padStart(4, '0');
|
||
await addStudent(data);
|
||
}
|
||
});
|
||
|
||
async function addStudent(data) {
|
||
try {
|
||
const res = await fetch(`${API_BASE}/students`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
if (res.ok) {
|
||
showToast('添加学生成功', 'success');
|
||
closeModal('student-modal');
|
||
loadStudents();
|
||
} else {
|
||
const err = await res.json();
|
||
showToast(err.detail || '添加失败', 'error');
|
||
}
|
||
} catch (err) {
|
||
showToast('网络错误', 'error');
|
||
}
|
||
}
|
||
|
||
async function updateStudent(data) {
|
||
try {
|
||
const res = await fetch(`${API_BASE}/students`, {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
if (res.ok) {
|
||
showToast('更新学生成功', 'success');
|
||
closeModal('student-modal');
|
||
loadStudents();
|
||
} else {
|
||
const err = await res.json();
|
||
showToast(err.detail || '更新失败', 'error');
|
||
}
|
||
} catch (err) {
|
||
showToast('网络错误', 'error');
|
||
}
|
||
}
|
||
|
||
function deleteStudent(id, name) {
|
||
showConfirm(`确定要删除学生 ${name} 吗?此操作不可恢复。`, async () => {
|
||
try {
|
||
const res = await fetch(`${API_BASE}/students`, {
|
||
method: 'DELETE',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ id })
|
||
});
|
||
|
||
if (res.ok) {
|
||
showToast('删除成功', 'success');
|
||
loadStudents();
|
||
} else {
|
||
showToast('删除失败', 'error');
|
||
}
|
||
} catch (err) {
|
||
showToast('网络错误', 'error');
|
||
}
|
||
});
|
||
}
|
||
|
||
function updateStudentSelect() {
|
||
const select = document.getElementById('score-student-filter');
|
||
select.innerHTML = '<option value="">选择学生</option>' +
|
||
studentData.map(s => `<option value="${s.id}">${s.name} (${s.num})</option>`).join('');
|
||
}
|
||
|
||
// ── 教师管理 ──────────────────────────────────────
|
||
async function loadTeachers() {
|
||
try {
|
||
console.log('[DEBUG] 开始加载教师...');
|
||
const res = await fetch(`${API_BASE}/tea`);
|
||
console.log('[DEBUG] 响应状态:', res.status);
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
console.log('[DEBUG] 教师数据:', JSON.stringify(data).substring(0, 200));
|
||
teacherData = data.data || [];
|
||
console.log('[DEBUG] 教师条数:', teacherData.length);
|
||
renderTeachers(teacherData);
|
||
} else {
|
||
console.error('[DEBUG] 请求失败:', res.status, res.statusText);
|
||
}
|
||
} catch (err) {
|
||
console.error('加载教师失败:', err);
|
||
}
|
||
}
|
||
|
||
function renderTeachers(data) {
|
||
const tbody = document.getElementById('teachers-tbody');
|
||
tbody.innerHTML = data.map(t => `
|
||
<tr>
|
||
<td>${t.id}</td>
|
||
<td>${t.name}</td>
|
||
<td><span class="tag ${t.sex === '男' ? 'tag-primary' : t.sex === '女' ? 'tag-warning' : ''}">${t.sex || '-'}</span></td>
|
||
<td>${t.specialty || '-'}</td>
|
||
<td>${t.work_experience || '-'}</td>
|
||
<td>${t.collage || '-'}</td>
|
||
<td>
|
||
<button class="btn btn-sm btn-outline">编辑</button>
|
||
<button class="btn btn-sm btn-danger" onclick="deleteTeacher(${t.id}, '${t.name}')">删除</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function searchTeachers() {
|
||
const keyword = document.getElementById('teacher-search').value.toLowerCase();
|
||
const filtered = teacherData.filter(t => t.name.toLowerCase().includes(keyword));
|
||
renderTeachers(filtered);
|
||
}
|
||
|
||
document.getElementById('teacher-form').addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
|
||
const data = {
|
||
name: document.getElementById('teacher-name').value,
|
||
sex: document.getElementById('teacher-sex').value,
|
||
phone: document.getElementById('teacher-phone').value,
|
||
class_id: parseInt(document.getElementById('teacher-class-id').value),
|
||
sub: document.getElementById('teacher-subject-id').value,
|
||
specialty: document.getElementById('teacher-specialty').value,
|
||
education: document.getElementById('teacher-education').value,
|
||
collage: document.getElementById('teacher-collage').value,
|
||
};
|
||
|
||
try {
|
||
const res = await fetch(`${API_BASE}/tea`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
if (res.ok) {
|
||
showToast('添加教师成功', 'success');
|
||
closeModal('teacher-modal');
|
||
loadTeachers();
|
||
} else {
|
||
showToast('添加失败', 'error');
|
||
}
|
||
} catch (err) {
|
||
showToast('网络错误', 'error');
|
||
}
|
||
});
|
||
|
||
function deleteTeacher(id, name) {
|
||
showConfirm(`确定要删除教师 ${name} 吗?`, async () => {
|
||
try {
|
||
const res = await fetch(`${API_BASE}/tea/${id}`, { method: 'DELETE' });
|
||
if (res.ok) {
|
||
showToast('删除成功', 'success');
|
||
loadTeachers();
|
||
} else {
|
||
showToast('删除失败', 'error');
|
||
}
|
||
} catch (err) {
|
||
showToast('网络错误', 'error');
|
||
}
|
||
});
|
||
}
|
||
|
||
// ── 就业管理 ──────────────────────────────────────
|
||
async function loadEmployment() {
|
||
try {
|
||
console.log('[DEBUG] 开始加载就业...');
|
||
const res = await fetch(`${API_BASE}/emp`);
|
||
console.log('[DEBUG] 响应状态:', res.status);
|
||
if (res.ok) {
|
||
const resp = await res.json();
|
||
console.log('[DEBUG] 就业响应类型:', Array.isArray(resp) ? 'array' : 'object');
|
||
employmentData = Array.isArray(resp) ? resp : (resp.data || []);
|
||
console.log('[DEBUG] 就业条数:', employmentData.length);
|
||
renderEmployment(employmentData);
|
||
} else {
|
||
console.error('[DEBUG] 请求失败:', res.status, res.statusText);
|
||
}
|
||
} catch (err) {
|
||
console.error('加载就业数据失败:', err);
|
||
}
|
||
}
|
||
|
||
function renderEmployment(data) {
|
||
const tbody = document.getElementById('employment-tbody');
|
||
tbody.innerHTML = data.map(e => `
|
||
<tr>
|
||
<td>${e['学生姓名'] || '-'}</td>
|
||
<td>班级${e['学生班级编号'] || '-'}</td>
|
||
<td>${e['公司'] || '-'}</td>
|
||
<td>¥${(e['薪资'] || 0).toLocaleString()}</td>
|
||
<td>${e['offer下发时间'] || '待定'}</td>
|
||
<td><span class="tag ${e['offer下发时间'] ? 'tag-success' : 'tag-warning'}">${e['offer下发时间'] ? '已就业' : '待就业'}</span></td>
|
||
<td>
|
||
<button class="btn btn-sm btn-outline">编辑</button>
|
||
<button class="btn btn-sm btn-danger" onclick="deleteEmployment(${e.id})">删除</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function searchEmployment() {
|
||
const keyword = document.getElementById('employment-search').value.toLowerCase();
|
||
const filtered = employmentData.filter(e =>
|
||
((e['学生姓名'] && e['学生姓名'].toLowerCase().includes(keyword)) ||
|
||
(e['公司'] && e['公司'].toLowerCase().includes(keyword)))
|
||
);
|
||
renderEmployment(filtered);
|
||
}
|
||
|
||
document.getElementById('employment-form').addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
|
||
const data = {
|
||
sname: '',
|
||
class_id: parseInt(document.getElementById('emp-class-id').value),
|
||
company_id: parseInt(document.getElementById('emp-company-id').value) || null,
|
||
employment_salary: parseInt(document.getElementById('emp-salary').value) || null,
|
||
employment_open_time: document.getElementById('emp-open-time').value || null,
|
||
offer_recived_time: document.getElementById('emp-offer-time').value || null,
|
||
};
|
||
|
||
try {
|
||
const res = await fetch(`${API_BASE}/emp`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
if (res.ok) {
|
||
showToast('添加就业信息成功', 'success');
|
||
closeModal('employment-modal');
|
||
loadEmployment();
|
||
} else {
|
||
showToast('添加失败', 'error');
|
||
}
|
||
} catch (err) {
|
||
showToast('网络错误', 'error');
|
||
}
|
||
});
|
||
|
||
function deleteEmployment(id) {
|
||
showConfirm('确定要删除这条就业记录吗?', async () => {
|
||
try {
|
||
const res = await fetch(`${API_BASE}/emp/${id}`, { method: 'DELETE' });
|
||
if (res.ok) {
|
||
showToast('删除成功', 'success');
|
||
loadEmployment();
|
||
} else {
|
||
showToast('删除失败', 'error');
|
||
}
|
||
} catch (err) {
|
||
showToast('网络错误', 'error');
|
||
}
|
||
});
|
||
}
|
||
|
||
// ── 成绩查询 ──────────────────────────────────────
|
||
async function loadScores() {
|
||
const studentId = document.getElementById('score-student-filter').value;
|
||
const term = document.getElementById('score-term-filter').value;
|
||
|
||
if (!studentId) {
|
||
document.getElementById('score-cards').innerHTML = '<p style="color: var(--text-muted); padding: 20px;">请选择学生进行查询</p>';
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const res = await fetch(`${API_BASE}/Score?sid=${studentId}&num=${term}`);
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
renderScores(data);
|
||
}
|
||
} catch (err) {
|
||
showToast('加载成绩失败', 'error');
|
||
}
|
||
}
|
||
|
||
function renderScores(scores) {
|
||
const container = document.getElementById('score-cards');
|
||
|
||
if (!scores || scores.length === 0) {
|
||
container.innerHTML = '<p style="color: var(--text-muted); padding: 20px;">暂无成绩数据</p>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = scores.map(s => `
|
||
<div class="score-card card-click">
|
||
<div class="score-card-header">
|
||
<span class="score-card-title">课程ID: ${s.t_subject}</span>
|
||
<span class="tag ${s.score >= 60 ? 'tag-success' : 'tag-danger'}">${s.score >= 60 ? '及格' : '不及格'}</span>
|
||
</div>
|
||
<div class="score-card-value ${s.score >= 60 ? 'pass' : 'fail'}">${s.score}分</div>
|
||
</div>
|
||
`).join('');
|
||
|
||
renderScoreChart(scores);
|
||
}
|
||
|
||
let scoreChart = null;
|
||
function renderScoreChart(scores) {
|
||
const ctx = document.getElementById('score-chart').getContext('2d');
|
||
|
||
if (scoreChart) {
|
||
scoreChart.destroy();
|
||
}
|
||
|
||
scoreChart = new Chart(ctx, {
|
||
type: 'bar',
|
||
data: {
|
||
labels: scores.map((s, i) => `课程${i + 1}`),
|
||
datasets: [{
|
||
label: '成绩',
|
||
data: scores.map(s => s.score),
|
||
backgroundColor: scores.map(s => s.score >= 60 ? '#4CAF50' : '#F44336'),
|
||
borderRadius: 4
|
||
}]
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
plugins: {
|
||
legend: { display: false }
|
||
},
|
||
scales: {
|
||
y: {
|
||
beginAtZero: true,
|
||
max: 100
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// ── 通用函数 ──────────────────────────────────────
|
||
function openModal(id) {
|
||
document.getElementById(id).classList.add('active');
|
||
}
|
||
|
||
function closeModal(id) {
|
||
document.getElementById(id).classList.remove('active');
|
||
if (id === 'student-modal') {
|
||
document.getElementById('student-form').reset();
|
||
document.getElementById('student-edit-id').value = '';
|
||
document.getElementById('student-modal-title').textContent = '新增学生';
|
||
}
|
||
}
|
||
|
||
function showToast(message, type = 'info') {
|
||
const container = document.getElementById('toast-container');
|
||
const toast = document.createElement('div');
|
||
toast.className = `toast ${type}`;
|
||
toast.innerHTML = `
|
||
<span>${type === 'success' ? '✓' : type === 'error' ? '✗' : 'ℹ'}</span>
|
||
<span>${message}</span>
|
||
`;
|
||
container.appendChild(toast);
|
||
|
||
setTimeout(() => {
|
||
toast.style.opacity = '0';
|
||
toast.style.transform = 'translateX(100%)';
|
||
setTimeout(() => toast.remove(), 300);
|
||
}, 3000);
|
||
}
|
||
|
||
function showConfirm(message, onConfirm) {
|
||
document.getElementById('confirm-message').textContent = message;
|
||
openModal('confirm-modal');
|
||
|
||
document.getElementById('confirm-btn').onclick = () => {
|
||
closeModal('confirm-modal');
|
||
onConfirm();
|
||
};
|
||
}
|
||
|
||
function sortTable(tableId, colIndex) {
|
||
const key = `${tableId}-${colIndex}`;
|
||
sortState[key] = !sortState[key];
|
||
const asc = sortState[key];
|
||
|
||
// 简化排序逻辑
|
||
showToast(`按第${colIndex + 1}列${asc ? '升序' : '降序'}排序`, 'info');
|
||
}
|
||
|
||
function changePage(type, delta) {
|
||
showToast(`${type}分页功能开发中`, 'info');
|
||
}
|
||
|
||
// ── 统计分析 ──────────────────────────────────────
|
||
async function loadClassStats() {
|
||
try {
|
||
const type = document.getElementById('stat-class-type').value;
|
||
const res = await fetch(`${API_BASE}/statcalc/students?headcount=${encodeURIComponent(type)}`);
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
const resultDiv = document.getElementById('class-stats-result');
|
||
resultDiv.innerHTML = data.length > 0
|
||
? data.map(row => `<div>${Object.values(row).join(' | ')}</div>`).join('')
|
||
: '<div>暂无数据</div>';
|
||
}
|
||
} catch (err) {
|
||
console.error('加载班级统计失败:', err);
|
||
}
|
||
}
|
||
|
||
async function loadExcellentStats() {
|
||
try {
|
||
const res = await fetch(`${API_BASE}/statcalc/scores`);
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
const resultDiv = document.getElementById('excellent-stats-result');
|
||
if (data.detail) {
|
||
resultDiv.innerHTML = `<div>${data.detail}</div>`;
|
||
} else {
|
||
resultDiv.innerHTML = data.length > 0
|
||
? data.map(row => `<div>${row.name} - ${row.score}分</div>`).join('')
|
||
: '<div>暂无数据</div>';
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('加载优秀成绩统计失败:', err);
|
||
}
|
||
}
|
||
|
||
async function loadFailStats() {
|
||
try {
|
||
const res = await fetch(`${API_BASE}/statcalc/fail`);
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
const resultDiv = document.getElementById('fail-stats-result');
|
||
if (data.detail) {
|
||
resultDiv.innerHTML = `<div>${data.detail}</div>`;
|
||
} else {
|
||
resultDiv.innerHTML = data.length > 0
|
||
? data.map(row => `<div>${row.name} | ${row.class_name} | 不及格${row.score}分</div>`).join('')
|
||
: '<div>无不及格学生</div>';
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('加载不及格统计失败:', err);
|
||
}
|
||
}
|
||
|
||
async function loadAvgStats() {
|
||
try {
|
||
const res = await fetch(`${API_BASE}/statcalc/average`);
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
const resultDiv = document.getElementById('avg-stats-result');
|
||
resultDiv.innerHTML = data.length > 0
|
||
? data.map(row => `<div>${row.class_name}班 - 平均分: ${row.avg_score}</div>`).join('')
|
||
: '<div>暂无数据</div>';
|
||
}
|
||
} catch (err) {
|
||
console.error('加载平均分统计失败:', err);
|
||
}
|
||
}
|
||
|
||
async function loadTopStats() {
|
||
try {
|
||
const res = await fetch(`${API_BASE}/statcalc/top`);
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
const resultDiv = document.getElementById('top-stats-result');
|
||
resultDiv.innerHTML = data.length > 0
|
||
? data.map((row, i) => `<div>${i+1}. ${row['学生姓名']} - ${row['公司']} - ¥${row['employment_salary']}</div>`).join('')
|
||
: '<div>暂无就业数据</div>';
|
||
}
|
||
} catch (err) {
|
||
console.error('加载Top5统计失败:', err);
|
||
}
|
||
}
|
||
|
||
async function loadJobStats() {
|
||
try {
|
||
const res = await fetch(`${API_BASE}/statcalc/job`);
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
const resultDiv = document.getElementById('job-stats-result');
|
||
resultDiv.innerHTML = data.length > 0
|
||
? data.map(row => `<div>${row['学生姓名']} - ${row['就业时常']}</div>`).join('')
|
||
: '<div>暂无数据</div>';
|
||
}
|
||
} catch (err) {
|
||
console.error('加载就业时长统计失败:', err);
|
||
}
|
||
}
|
||
|
||
async function loadAvgJobStats() {
|
||
try {
|
||
const res = await fetch(`${API_BASE}/statcalc/avgjob`);
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
const resultDiv = document.getElementById('avgjob-stats-result');
|
||
resultDiv.innerHTML = `
|
||
<div>就业人数: ${data['人数']}人</div>
|
||
<div>平均就业时长: ${data['平均就业时常(天)']}天</div>
|
||
`;
|
||
}
|
||
} catch (err) {
|
||
console.error('加载平均就业时长失败:', err);
|
||
}
|
||
}
|
||
|
||
async function loadAgeStats() {
|
||
try {
|
||
const res = await fetch(`${API_BASE}/statcalc/age`);
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
const resultDiv = document.getElementById('age-stats-result');
|
||
resultDiv.innerHTML = data.length > 0
|
||
? data.map(row => `<div>${row.name} - ${row.age}岁 - ${row.num}</div>`).join('')
|
||
: '<div>无30岁以上学员</div>';
|
||
}
|
||
} catch (err) {
|
||
console.error('加载年龄统计失败:', err);
|
||
}
|
||
}
|
||
|
||
// 点击模态框外部关闭
|
||
document.querySelectorAll('.modal-overlay').forEach(overlay => {
|
||
overlay.addEventListener('click', (e) => {
|
||
if (e.target === overlay) {
|
||
overlay.classList.remove('active');
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|