43 changed files with 945 additions and 8 deletions
|
@ -0,0 +1,33 @@ |
|||
spring.application.name=hnu-course-analysis |
|||
|
|||
server.port=8084 |
|||
|
|||
# SQLite数据库配置 |
|||
spring.datasource.url=jdbc:sqlite:course.db |
|||
spring.datasource.driver-class-name=org.sqlite.JDBC |
|||
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect |
|||
|
|||
spring.jpa.hibernate.ddl-auto=update |
|||
spring.jpa.show-sql=true |
|||
spring.jpa.properties.hibernate.format_sql=true |
|||
spring.jpa.open-in-view=false |
|||
|
|||
spring.sql.init.mode=always |
|||
spring.sql.init.schema-locations=classpath:schema.sql |
|||
|
|||
spring.thymeleaf.cache=false |
|||
spring.thymeleaf.prefix=classpath:/templates/ |
|||
spring.thymeleaf.suffix=.html |
|||
|
|||
logging.level.root=INFO |
|||
logging.level.com.example=DEBUG |
|||
|
|||
# 湖南大学选课系统配置 |
|||
crawler.hnu.login.url=https://jwxt.hnu.edu.cn/xtgl/login_login.html |
|||
crawler.hnu.username=your_username |
|||
crawler.hnu.password=your_password |
|||
crawler.hnu.course.list.url=https://jwxt.hnu.edu.cn/kbcx/xskbcx_cxXsKb.html |
|||
|
|||
# Selenium配置 |
|||
selenium.webdriver.chrome.driver.path=C:/chromedriver/chromedriver.exe |
|||
selenium.webdriver.headless=false |
|||
@ -0,0 +1,31 @@ |
|||
-- 湖南大学选课系统数据库初始化脚本 |
|||
|
|||
-- 创建课程表 |
|||
CREATE TABLE IF NOT EXISTS courses ( |
|||
id INTEGER PRIMARY KEY AUTOINCREMENT, |
|||
course_code TEXT, |
|||
course_name TEXT, |
|||
credit REAL, |
|||
teacher TEXT, |
|||
department TEXT, |
|||
capacity INTEGER, |
|||
enrolled INTEGER, |
|||
class_time TEXT, |
|||
class_room TEXT, |
|||
course_type TEXT, |
|||
semester TEXT, |
|||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP |
|||
); |
|||
|
|||
-- 插入测试数据 |
|||
INSERT INTO courses (course_code, course_name, credit, teacher, department, capacity, enrolled, class_time, class_room, course_type, semester) VALUES |
|||
('CS101', '计算机科学导论', 3.0, '张教授', '计算机科学与技术学院', 100, 85, '周一 1-2节', 'A101', '必修课', '2024-2025-1'), |
|||
('CS102', '数据结构', 4.0, '李教授', '计算机科学与技术学院', 80, 70, '周二 3-4节', 'B202', '必修课', '2024-2025-1'), |
|||
('CS103', '算法设计', 3.0, '王教授', '计算机科学与技术学院', 60, 55, '周三 5-6节', 'C303', '选修课', '2024-2025-1'), |
|||
('MA101', '高等数学', 5.0, '赵教授', '数学学院', 120, 110, '周一 3-4节', 'D404', '必修课', '2024-2025-1'), |
|||
('EN101', '大学英语', 3.0, '刘教授', '外国语学院', 100, 90, '周二 1-2节', 'E505', '必修课', '2024-2025-1'), |
|||
('PH101', '大学物理', 4.0, '钱教授', '物理学院', 90, 85, '周三 1-2节', 'F606', '必修课', '2024-2025-1'), |
|||
('CH101', '大学化学', 3.0, '孙教授', '化学学院', 70, 65, '周四 3-4节', 'G707', '选修课', '2024-2025-1'), |
|||
('HI101', '中国近现代史', 2.0, '周教授', '历史学院', 150, 145, '周五 1-2节', 'H808', '必修课', '2024-2025-1'), |
|||
('PE101', '体育', 1.0, '吴教授', '体育学院', 50, 50, '周三 7-8节', '体育馆', '必修课', '2024-2025-1'), |
|||
('AR101', '艺术鉴赏', 2.0, '郑教授', '艺术学院', 80, 75, '周四 5-6节', '艺术楼', '选修课', '2024-2025-1'); |
|||
@ -0,0 +1,328 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>数据分析 - 湖南大学选课系统分析</title> |
|||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> |
|||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script> |
|||
<style> |
|||
body { |
|||
font-family: 'Microsoft YaHei', sans-serif; |
|||
background-color: #f8f9fa; |
|||
} |
|||
.hero { |
|||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|||
color: white; |
|||
padding: 40px 0; |
|||
margin-bottom: 30px; |
|||
} |
|||
.card { |
|||
border-radius: 10px; |
|||
box-shadow: 0 4px 6px rgba(0,0,0,0.1); |
|||
margin-bottom: 30px; |
|||
} |
|||
.card-header { |
|||
background-color: #667eea; |
|||
color: white; |
|||
border-radius: 10px 10px 0 0; |
|||
} |
|||
.btn-primary { |
|||
background-color: #667eea; |
|||
border-color: #667eea; |
|||
} |
|||
.btn-primary:hover { |
|||
background-color: #5a6fd8; |
|||
border-color: #5a6fd8; |
|||
} |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<!-- 导航栏 --> |
|||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> |
|||
<div class="container"> |
|||
<a class="navbar-brand" href="/">湖南大学选课系统分析</a> |
|||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"> |
|||
<span class="navbar-toggler-icon"></span> |
|||
</button> |
|||
<div class="collapse navbar-collapse" id="navbarNav"> |
|||
<ul class="navbar-nav ms-auto"> |
|||
<li class="nav-item"> |
|||
<a class="nav-link" href="/">首页</a> |
|||
</li> |
|||
<li class="nav-item"> |
|||
<a class="nav-link" href="/courses">课程列表</a> |
|||
</li> |
|||
<li class="nav-item"> |
|||
<a class="nav-link active" href="/analysis">数据分析</a> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</nav> |
|||
|
|||
<!-- 英雄区 --> |
|||
<div class="hero"> |
|||
<div class="container"> |
|||
<h1 class="display-4">数据分析</h1> |
|||
<p class="lead">深入分析选课系统数据,获取有价值的洞察</p> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 分析图表 --> |
|||
<div class="container mb-5"> |
|||
<!-- 课程类型分布 --> |
|||
<div class="card"> |
|||
<div class="card-header"> |
|||
<h3>课程类型分布</h3> |
|||
</div> |
|||
<div class="card-body"> |
|||
<div id="courseTypeChart" style="width: 100%; height: 400px;"></div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 院系分布 --> |
|||
<div class="card"> |
|||
<div class="card-header"> |
|||
<h3>院系课程分布</h3> |
|||
</div> |
|||
<div class="card-body"> |
|||
<div id="departmentChart" style="width: 100%; height: 400px;"></div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 学分分布 --> |
|||
<div class="card"> |
|||
<div class="card-header"> |
|||
<h3>学分分布</h3> |
|||
</div> |
|||
<div class="card-body"> |
|||
<div id="creditChart" style="width: 100%; height: 400px;"></div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 课程使用率 --> |
|||
<div class="card"> |
|||
<div class="card-header"> |
|||
<h3>课程容量使用率</h3> |
|||
</div> |
|||
<div class="card-body"> |
|||
<div id="usageRateChart" style="width: 100%; height: 400px;"></div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 院系统计 --> |
|||
<div class="card"> |
|||
<div class="card-header"> |
|||
<h3>院系统计详情</h3> |
|||
</div> |
|||
<div class="card-body"> |
|||
<div class="table-responsive"> |
|||
<table class="table table-striped"> |
|||
<thead> |
|||
<tr> |
|||
<th>院系</th> |
|||
<th>课程数</th> |
|||
<th>总学分</th> |
|||
<th>总容量</th> |
|||
<th>总已选</th> |
|||
<th>使用率</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr th:each="dept : ${departmentStats}"> |
|||
<td th:text="${dept.key}"></td> |
|||
<td th:text="${dept.value.courseCount}"></td> |
|||
<td th:text="${dept.value.totalCredits}"></td> |
|||
<td th:text="${dept.value.totalCapacity}"></td> |
|||
<td th:text="${dept.value.totalEnrolled}"></td> |
|||
<td th:text="${#numbers.formatDecimal(dept.value.usageRate, 0, 1)}%"></td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 页脚 --> |
|||
<footer class="bg-dark text-white py-4"> |
|||
<div class="container text-center"> |
|||
<p>© 2024 湖南大学选课系统分析平台</p> |
|||
</div> |
|||
</footer> |
|||
|
|||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> |
|||
<script> |
|||
// 初始化课程类型分布图表 |
|||
fetch('/api/course-type-distribution') |
|||
.then(response => response.json()) |
|||
.then(data => { |
|||
const types = Object.keys(data); |
|||
const counts = Object.values(data); |
|||
|
|||
const chart = echarts.init(document.getElementById('courseTypeChart')); |
|||
chart.setOption({ |
|||
title: { |
|||
text: '课程类型分布', |
|||
left: 'center' |
|||
}, |
|||
tooltip: { |
|||
trigger: 'item' |
|||
}, |
|||
legend: { |
|||
orient: 'vertical', |
|||
left: 'left' |
|||
}, |
|||
series: [{ |
|||
name: '课程类型', |
|||
type: 'pie', |
|||
radius: '60%', |
|||
data: types.map((type, index) => ({ |
|||
name: type, |
|||
value: counts[index] |
|||
})), |
|||
emphasis: { |
|||
itemStyle: { |
|||
shadowBlur: 10, |
|||
shadowOffsetX: 0, |
|||
shadowColor: 'rgba(0, 0, 0, 0.5)' |
|||
} |
|||
} |
|||
}] |
|||
}); |
|||
}); |
|||
|
|||
// 初始化院系分布图表 |
|||
fetch('/api/department-distribution') |
|||
.then(response => response.json()) |
|||
.then(data => { |
|||
const departments = Object.keys(data); |
|||
const counts = Object.values(data); |
|||
|
|||
const chart = echarts.init(document.getElementById('departmentChart')); |
|||
chart.setOption({ |
|||
title: { |
|||
text: '院系课程分布', |
|||
left: 'center' |
|||
}, |
|||
tooltip: { |
|||
trigger: 'item' |
|||
}, |
|||
legend: { |
|||
orient: 'vertical', |
|||
left: 'left' |
|||
}, |
|||
series: [{ |
|||
name: '院系', |
|||
type: 'pie', |
|||
radius: '60%', |
|||
data: departments.map((dept, index) => ({ |
|||
name: dept, |
|||
value: counts[index] |
|||
})), |
|||
emphasis: { |
|||
itemStyle: { |
|||
shadowBlur: 10, |
|||
shadowOffsetX: 0, |
|||
shadowColor: 'rgba(0, 0, 0, 0.5)' |
|||
} |
|||
} |
|||
}] |
|||
}); |
|||
}); |
|||
|
|||
// 初始化学分分布图表 |
|||
fetch('/api/credit-distribution') |
|||
.then(response => response.json()) |
|||
.then(data => { |
|||
const credits = Object.keys(data).map(Number).sort((a, b) => a - b); |
|||
const counts = credits.map(credit => data[credit]); |
|||
|
|||
const chart = echarts.init(document.getElementById('creditChart')); |
|||
chart.setOption({ |
|||
title: { |
|||
text: '学分分布', |
|||
left: 'center' |
|||
}, |
|||
tooltip: { |
|||
trigger: 'axis' |
|||
}, |
|||
xAxis: { |
|||
type: 'category', |
|||
data: credits.map(c => c + '学分'), |
|||
name: '学分' |
|||
}, |
|||
yAxis: { |
|||
type: 'value', |
|||
name: '课程数' |
|||
}, |
|||
series: [{ |
|||
data: counts, |
|||
type: 'bar', |
|||
itemStyle: { |
|||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
|||
{offset: 0, color: '#667eea'}, |
|||
{offset: 1, color: '#764ba2'} |
|||
]) |
|||
} |
|||
}] |
|||
}); |
|||
}); |
|||
|
|||
// 初始化课程使用率图表 |
|||
fetch('/api/top-courses') |
|||
.then(response => response.json()) |
|||
.then(data => { |
|||
const courseNames = data.map(item => item.course_name); |
|||
const enrolledCounts = data.map(item => item.enrolled); |
|||
|
|||
const chart = echarts.init(document.getElementById('usageRateChart')); |
|||
chart.setOption({ |
|||
title: { |
|||
text: '热门课程使用率', |
|||
left: 'center' |
|||
}, |
|||
tooltip: { |
|||
trigger: 'axis', |
|||
axisPointer: { |
|||
type: 'shadow' |
|||
} |
|||
}, |
|||
xAxis: { |
|||
type: 'category', |
|||
data: courseNames, |
|||
axisLabel: { |
|||
rotate: 45 |
|||
} |
|||
}, |
|||
yAxis: { |
|||
type: 'value', |
|||
name: '已选人数' |
|||
}, |
|||
series: [{ |
|||
data: enrolledCounts, |
|||
type: 'bar', |
|||
itemStyle: { |
|||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
|||
{offset: 0, color: '#667eea'}, |
|||
{offset: 1, color: '#764ba2'} |
|||
]) |
|||
} |
|||
}] |
|||
}); |
|||
}); |
|||
|
|||
// 响应式调整 |
|||
window.addEventListener('resize', function() { |
|||
const charts = ['courseTypeChart', 'departmentChart', 'creditChart', 'usageRateChart']; |
|||
charts.forEach(id => { |
|||
const chart = echarts.getInstanceByDom(document.getElementById(id)); |
|||
if (chart) { |
|||
chart.resize(); |
|||
} |
|||
}); |
|||
}); |
|||
</script> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,169 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>课程列表 - 湖南大学选课系统分析</title> |
|||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> |
|||
<style> |
|||
body { |
|||
font-family: 'Microsoft YaHei', sans-serif; |
|||
background-color: #f8f9fa; |
|||
} |
|||
.hero { |
|||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|||
color: white; |
|||
padding: 40px 0; |
|||
margin-bottom: 30px; |
|||
} |
|||
.card { |
|||
border-radius: 10px; |
|||
box-shadow: 0 4px 6px rgba(0,0,0,0.1); |
|||
} |
|||
.table-container { |
|||
background: white; |
|||
border-radius: 10px; |
|||
box-shadow: 0 4px 6px rgba(0,0,0,0.1); |
|||
padding: 20px; |
|||
} |
|||
.btn-primary { |
|||
background-color: #667eea; |
|||
border-color: #667eea; |
|||
} |
|||
.btn-primary:hover { |
|||
background-color: #5a6fd8; |
|||
border-color: #5a6fd8; |
|||
} |
|||
.status-badge { |
|||
padding: 5px 10px; |
|||
border-radius: 20px; |
|||
font-size: 0.8rem; |
|||
} |
|||
.status-high { |
|||
background-color: #f8d7da; |
|||
color: #721c24; |
|||
} |
|||
.status-medium { |
|||
background-color: #fff3cd; |
|||
color: #856404; |
|||
} |
|||
.status-low { |
|||
background-color: #d4edda; |
|||
color: #155724; |
|||
} |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<!-- 导航栏 --> |
|||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> |
|||
<div class="container"> |
|||
<a class="navbar-brand" href="/">湖南大学选课系统分析</a> |
|||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"> |
|||
<span class="navbar-toggler-icon"></span> |
|||
</button> |
|||
<div class="collapse navbar-collapse" id="navbarNav"> |
|||
<ul class="navbar-nav ms-auto"> |
|||
<li class="nav-item"> |
|||
<a class="nav-link" href="/">首页</a> |
|||
</li> |
|||
<li class="nav-item"> |
|||
<a class="nav-link active" href="/courses">课程列表</a> |
|||
</li> |
|||
<li class="nav-item"> |
|||
<a class="nav-link" href="/analysis">数据分析</a> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</nav> |
|||
|
|||
<!-- 英雄区 --> |
|||
<div class="hero"> |
|||
<div class="container"> |
|||
<h1 class="display-4">课程列表</h1> |
|||
<p class="lead">查看所有课程的详细信息</p> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 课程列表 --> |
|||
<div class="container mb-5"> |
|||
<div class="table-container"> |
|||
<div class="d-flex justify-content-between align-items-center mb-4"> |
|||
<h2>课程详情</h2> |
|||
<button class="btn btn-primary" onclick="crawlData()"> |
|||
<i class="bi bi-download"></i> 刷新数据 |
|||
</button> |
|||
</div> |
|||
|
|||
<div class="table-responsive"> |
|||
<table class="table table-striped table-hover"> |
|||
<thead class="table-dark"> |
|||
<tr> |
|||
<th>课程代码</th> |
|||
<th>课程名称</th> |
|||
<th>学分</th> |
|||
<th>教师</th> |
|||
<th>院系</th> |
|||
<th>容量</th> |
|||
<th>已选</th> |
|||
<th>使用率</th> |
|||
<th>上课时间</th> |
|||
<th>上课地点</th> |
|||
<th>课程类型</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr th:each="course : ${courses}"> |
|||
<td th:text="${course.courseCode}"></td> |
|||
<td th:text="${course.courseName}"></td> |
|||
<td th:text="${course.credit}"></td> |
|||
<td th:text="${course.teacher}"></td> |
|||
<td th:text="${course.department}"></td> |
|||
<td th:text="${course.capacity}"></td> |
|||
<td th:text="${course.enrolled}"></td> |
|||
<td> |
|||
<span th:class="|status-badge ${course.enrolled * 100 / course.capacity > 80 ? 'status-high' : (course.enrolled * 100 / course.capacity > 50 ? 'status-medium' : 'status-low')}"> |
|||
<span th:text="${#numbers.formatDecimal(course.enrolled * 100 / course.capacity, 0, 1)}%"></span> |
|||
</span> |
|||
</td> |
|||
<td th:text="${course.classTime}"></td> |
|||
<td th:text="${course.classRoom}"></td> |
|||
<td th:text="${course.courseType}"></td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 页脚 --> |
|||
<footer class="bg-dark text-white py-4"> |
|||
<div class="container text-center"> |
|||
<p>© 2024 湖南大学选课系统分析平台</p> |
|||
</div> |
|||
</footer> |
|||
|
|||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> |
|||
<script> |
|||
// 爬取数据 |
|||
function crawlData() { |
|||
fetch('/crawl', { |
|||
method: 'POST' |
|||
}) |
|||
.then(response => response.json()) |
|||
.then(data => { |
|||
if (data.success) { |
|||
alert(data.message); |
|||
window.location.reload(); |
|||
} else { |
|||
alert('爬取失败'); |
|||
} |
|||
}) |
|||
.catch(error => { |
|||
console.error('Error:', error); |
|||
alert('爬取失败'); |
|||
}); |
|||
} |
|||
</script> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,24 @@ |
|||
<!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://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> |
|||
</head> |
|||
<body> |
|||
<div class="container mt-5"> |
|||
<div class="row justify-content-center"> |
|||
<div class="col-md-6"> |
|||
<div class="card"> |
|||
<div class="card-body text-center"> |
|||
<h1 class="text-danger">500 - 服务器内部错误</h1> |
|||
<p class="mt-3">服务器遇到了一个内部错误,请稍后再试。</p> |
|||
<a href="/" class="btn btn-primary mt-4">返回首页</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,318 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>湖南大学选课系统分析</title> |
|||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> |
|||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script> |
|||
<style> |
|||
body { |
|||
font-family: 'Microsoft YaHei', sans-serif; |
|||
background-color: #f8f9fa; |
|||
} |
|||
.hero { |
|||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|||
color: white; |
|||
padding: 60px 0; |
|||
margin-bottom: 30px; |
|||
} |
|||
.card { |
|||
border-radius: 10px; |
|||
box-shadow: 0 4px 6px rgba(0,0,0,0.1); |
|||
transition: transform 0.3s ease; |
|||
} |
|||
.card:hover { |
|||
transform: translateY(-5px); |
|||
} |
|||
.stat-card { |
|||
text-align: center; |
|||
padding: 20px; |
|||
} |
|||
.stat-value { |
|||
font-size: 2rem; |
|||
font-weight: bold; |
|||
color: #667eea; |
|||
} |
|||
.stat-label { |
|||
font-size: 1rem; |
|||
color: #6c757d; |
|||
} |
|||
.btn-primary { |
|||
background-color: #667eea; |
|||
border-color: #667eea; |
|||
} |
|||
.btn-primary:hover { |
|||
background-color: #5a6fd8; |
|||
border-color: #5a6fd8; |
|||
} |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<!-- 导航栏 --> |
|||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> |
|||
<div class="container"> |
|||
<a class="navbar-brand" href="/">湖南大学选课系统分析</a> |
|||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"> |
|||
<span class="navbar-toggler-icon"></span> |
|||
</button> |
|||
<div class="collapse navbar-collapse" id="navbarNav"> |
|||
<ul class="navbar-nav ms-auto"> |
|||
<li class="nav-item"> |
|||
<a class="nav-link active" href="/">首页</a> |
|||
</li> |
|||
<li class="nav-item"> |
|||
<a class="nav-link" href="/courses">课程列表</a> |
|||
</li> |
|||
<li class="nav-item"> |
|||
<a class="nav-link" href="/analysis">数据分析</a> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</nav> |
|||
|
|||
<!-- 英雄区 --> |
|||
<div class="hero"> |
|||
<div class="container"> |
|||
<h1 class="display-4">湖南大学选课系统数据分析平台</h1> |
|||
<p class="lead">实时爬取、分析和可视化展示选课系统数据</p> |
|||
<button class="btn btn-light btn-lg" onclick="crawlData()"> |
|||
<i class="bi bi-download"></i> 爬取最新数据 |
|||
</button> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 统计卡片 --> |
|||
<div class="container mb-5"> |
|||
<div class="row"> |
|||
<div class="col-md-3"> |
|||
<div class="card stat-card"> |
|||
<div class="card-body"> |
|||
<div class="stat-value" th:text="${overallStats.totalCourses}"></div> |
|||
<div class="stat-label">总课程数</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="col-md-3"> |
|||
<div class="card stat-card"> |
|||
<div class="card-body"> |
|||
<div class="stat-value" th:text="${overallStats.totalCredits}"></div> |
|||
<div class="stat-label">总学分</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="col-md-3"> |
|||
<div class="card stat-card"> |
|||
<div class="card-body"> |
|||
<div class="stat-value" th:text="${overallStats.totalEnrolled}"></div> |
|||
<div class="stat-label">总已选人数</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="col-md-3"> |
|||
<div class="card stat-card"> |
|||
<div class="card-body"> |
|||
<div class="stat-value" th:text="${#numbers.formatDecimal(overallStats.overallUsageRate, 0, 1)}%"> |
|||
使用率 |
|||
</div> |
|||
<div class="stat-label">总体使用率</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 热门课程 --> |
|||
<div class="container mb-5"> |
|||
<h2 class="text-center mb-4">热门课程排行</h2> |
|||
<div class="card"> |
|||
<div class="card-body"> |
|||
<div id="topCoursesChart" style="width: 100%; height: 400px;"></div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 课程类型分布 --> |
|||
<div class="container mb-5"> |
|||
<h2 class="text-center mb-4">课程类型分布</h2> |
|||
<div class="card"> |
|||
<div class="card-body"> |
|||
<div id="courseTypeChart" style="width: 100%; height: 400px;"></div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 院系分布 --> |
|||
<div class="container mb-5"> |
|||
<h2 class="text-center mb-4">院系课程分布</h2> |
|||
<div class="card"> |
|||
<div class="card-body"> |
|||
<div id="departmentChart" style="width: 100%; height: 400px;"></div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 页脚 --> |
|||
<footer class="bg-dark text-white py-4"> |
|||
<div class="container text-center"> |
|||
<p>© 2024 湖南大学选课系统分析平台</p> |
|||
</div> |
|||
</footer> |
|||
|
|||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> |
|||
<script> |
|||
// 爬取数据 |
|||
function crawlData() { |
|||
fetch('/crawl', { |
|||
method: 'POST' |
|||
}) |
|||
.then(response => response.json()) |
|||
.then(data => { |
|||
if (data.success) { |
|||
alert(data.message); |
|||
window.location.reload(); |
|||
} else { |
|||
alert('爬取失败'); |
|||
} |
|||
}) |
|||
.catch(error => { |
|||
console.error('Error:', error); |
|||
alert('爬取失败'); |
|||
}); |
|||
} |
|||
|
|||
// 初始化热门课程图表 |
|||
fetch('/api/top-courses') |
|||
.then(response => response.json()) |
|||
.then(data => { |
|||
const courseNames = data.map(item => item.course_name); |
|||
const enrolledCounts = data.map(item => item.enrolled); |
|||
|
|||
const chart = echarts.init(document.getElementById('topCoursesChart')); |
|||
chart.setOption({ |
|||
title: { |
|||
text: '热门课程排行', |
|||
left: 'center' |
|||
}, |
|||
tooltip: { |
|||
trigger: 'axis', |
|||
axisPointer: { |
|||
type: 'shadow' |
|||
} |
|||
}, |
|||
xAxis: { |
|||
type: 'category', |
|||
data: courseNames, |
|||
axisLabel: { |
|||
rotate: 45 |
|||
} |
|||
}, |
|||
yAxis: { |
|||
type: 'value', |
|||
name: '已选人数' |
|||
}, |
|||
series: [{ |
|||
data: enrolledCounts, |
|||
type: 'bar', |
|||
itemStyle: { |
|||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
|||
{offset: 0, color: '#667eea'}, |
|||
{offset: 1, color: '#764ba2'} |
|||
]) |
|||
} |
|||
}] |
|||
}); |
|||
}); |
|||
|
|||
// 初始化课程类型分布图表 |
|||
fetch('/api/course-type-distribution') |
|||
.then(response => response.json()) |
|||
.then(data => { |
|||
const types = Object.keys(data); |
|||
const counts = Object.values(data); |
|||
|
|||
const chart = echarts.init(document.getElementById('courseTypeChart')); |
|||
chart.setOption({ |
|||
title: { |
|||
text: '课程类型分布', |
|||
left: 'center' |
|||
}, |
|||
tooltip: { |
|||
trigger: 'item' |
|||
}, |
|||
legend: { |
|||
orient: 'vertical', |
|||
left: 'left' |
|||
}, |
|||
series: [{ |
|||
name: '课程类型', |
|||
type: 'pie', |
|||
radius: '60%', |
|||
data: types.map((type, index) => ({ |
|||
name: type, |
|||
value: counts[index] |
|||
})), |
|||
emphasis: { |
|||
itemStyle: { |
|||
shadowBlur: 10, |
|||
shadowOffsetX: 0, |
|||
shadowColor: 'rgba(0, 0, 0, 0.5)' |
|||
} |
|||
} |
|||
}] |
|||
}); |
|||
}); |
|||
|
|||
// 初始化院系分布图表 |
|||
fetch('/api/department-distribution') |
|||
.then(response => response.json()) |
|||
.then(data => { |
|||
const departments = Object.keys(data); |
|||
const counts = Object.values(data); |
|||
|
|||
const chart = echarts.init(document.getElementById('departmentChart')); |
|||
chart.setOption({ |
|||
title: { |
|||
text: '院系课程分布', |
|||
left: 'center' |
|||
}, |
|||
tooltip: { |
|||
trigger: 'item' |
|||
}, |
|||
legend: { |
|||
orient: 'vertical', |
|||
left: 'left' |
|||
}, |
|||
series: [{ |
|||
name: '院系', |
|||
type: 'pie', |
|||
radius: '60%', |
|||
data: departments.map((dept, index) => ({ |
|||
name: dept, |
|||
value: counts[index] |
|||
})), |
|||
emphasis: { |
|||
itemStyle: { |
|||
shadowBlur: 10, |
|||
shadowOffsetX: 0, |
|||
shadowColor: 'rgba(0, 0, 0, 0.5)' |
|||
} |
|||
} |
|||
}] |
|||
}); |
|||
}); |
|||
|
|||
// 响应式调整 |
|||
window.addEventListener('resize', function() { |
|||
const charts = ['topCoursesChart', 'courseTypeChart', 'departmentChart']; |
|||
charts.forEach(id => { |
|||
const chart = echarts.getInstanceByDom(document.getElementById(id)); |
|||
if (chart) { |
|||
chart.resize(); |
|||
} |
|||
}); |
|||
}); |
|||
</script> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,21 @@ |
|||
com\example\DatabaseUtil.class |
|||
com\example\exception\ParseException.class |
|||
com\example\entity\Course.class |
|||
com\example\exception\NetworkException.class |
|||
com\example\Untitled_2.class |
|||
com\example\HnuCourseSystem.class |
|||
com\example\CourseAnalysis.class |
|||
com\example\ExceptionTest.class |
|||
com\example\crawler\CrawlerContext.class |
|||
com\example\crawler\strategy\ParseStrategy.class |
|||
com\example\crawler\strategy\SduParseStrategy.class |
|||
com\example\crawler\CrawlerService.class |
|||
com\example\view\CourseView.class |
|||
com\example\CourseSystemTest.class |
|||
com\example\crawler\strategy\HnuParseStrategy.class |
|||
com\example\exception\CrawlerException.class |
|||
com\example\service\CourseService.class |
|||
com\example\Pair.class |
|||
com\example\controller\CourseController.class |
|||
com\example\repository\CourseRepository.class |
|||
com\example\exception\BizException.class |
|||
@ -0,0 +1,21 @@ |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\crawler\strategy\HnuParseStrategy.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\controller\CourseController.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\exception\NetworkException.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\view\CourseView.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\CourseAnalysis.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\crawler\CrawlerService.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\exception\CrawlerException.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\CourseSystemTest.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\ExceptionTest.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\HnuCourseSystem.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\DatabaseUtil.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\Untitled-2.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\exception\BizException.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\crawler\CrawlerContext.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\crawler\strategy\SduParseStrategy.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\repository\CourseRepository.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\service\CourseService.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\crawler\strategy\ParseStrategy.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\exception\ParseException.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\entity\Course.java |
|||
C:\Users\范馨遥\.trae-cn\course-analysis\src\main\java\com\example\Pair.java |
|||
@ -1,8 +0,0 @@ |
|||
@echo off |
|||
echo 正在编译项目... |
|||
javac -d target/classes src/main/java/com/example/entity/Course.java src/main/java/com/example/DatabaseUtil.java src/main/java/com/example/CourseAnalysis.java src/main/java/com/example/HnuCourseSystem.java src/main/java/com/example/CourseSystemTest.java |
|||
|
|||
echo 正在运行测试程序... |
|||
java -cp target/classes;C:\Users\范馨遥\.m2\repository\org\xerial\sqlite-jdbc\3.44.1.0\sqlite-jdbc-3.44.1.0.jar com.example.CourseSystemTest |
|||
|
|||
pause |
|||
Loading…
Reference in new issue