Compare commits

..

25 Commits

Author SHA1 Message Date
32e1af6bf2 更新 requirements.txt 2023-12-31 00:23:03 +08:00
b5dd4b6d7d 1 2023-12-30 22:44:36 +08:00
cf2ff28e58 更新 login.html 2023-12-30 21:31:47 +08:00
58c4b85e6a 删除 requirements.txt 2023-12-29 17:31:25 +08:00
b6fc41994e 创建 ReceiveExcelTest.py 2023-12-29 17:31:23 +08:00
1a08ba460c 创建 StudentSignInTest.py 2023-12-29 17:25:11 +08:00
692631e395 创建 TeacherSignInTest.py 2023-12-29 17:25:09 +08:00
1174b225a7 创建 TestGetHtml.py 2023-12-29 17:25:05 +08:00
ded28f6a1e 创建 TestLogout.py 2023-12-29 16:31:56 +08:00
4e64fb2422 创建 TestGetMenu.py 2023-12-29 16:31:55 +08:00
eb074e5dd5 更新 database_manager.py 2023-12-29 16:07:22 +08:00
6ef3faf149 创建 TestLogin.py 2023-12-29 16:07:20 +08:00
9b519f4650 更新 login.html 2023-12-29 16:04:57 +08:00
d6bef5812b 删除 init.py 2023-12-29 16:04:54 +08:00
8e8fccbb1a 创建 app.py 2023-12-29 16:04:52 +08:00
237f0acf8d 删除 app.py 2023-12-29 16:04:50 +08:00
44ce96c7d9 更新 views.py 2023-12-29 16:04:42 +08:00
dae0ce21be 更新 announcement.html 2023-12-29 15:19:49 +08:00
a94dc7b415 更新 app.py 2023-12-29 15:06:04 +08:00
ca44cf0eaa 创建 requirements.txt 2023-12-29 15:05:23 +08:00
18c3968e59 更新 database_manager.py 2023-12-29 15:02:57 +08:00
5ce9ee1b1e 更新 mysql.sql 2023-12-29 14:47:07 +08:00
2596680c3a 更新 views.py 2023-12-29 14:47:05 +08:00
ff030444e4 更新 mysql.sql 2023-12-29 14:39:51 +08:00
e14688471a 更新 mysql.sql 2023-12-29 14:31:29 +08:00
23 changed files with 679 additions and 219 deletions

View File

@@ -0,0 +1,2 @@
import views

View File

File diff suppressed because one or more lines are too long

View File

@@ -8,4 +8,4 @@ $.get('/api/menu', function (menuItems) {
});
layui.element.render('nav', 'test');
});
});

View File

@@ -7,6 +7,47 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link href="static/css/layui.css" rel="stylesheet"/>
<style>
/* 容器样式 */
.layui-container {
padding: 20px;
background-color: #f7f7f7;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
/* 标题样式 */
.layui-header {
margin-bottom: 20px;
font-size: 2em; /* 更大的标题字体 */
color: #333;
border-bottom: 2px solid #e2e2e2;
padding-bottom: 10px;
}
/* 课程信息样式 */
.layui-field-box p {
font-size: 1.2em; /* 更大的文字 */
margin: 10px 0; /* 调整段落间距 */
padding: 10px; /* 内边距 */
background-color: #fff; /* 背景色 */
border-radius: 4px; /* 轻微的圆角 */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* 细微的阴影 */
}
/* 字段集合样式调整 */
fieldset.layui-elem-field {
border-color: #ddd; /* 更淡的边框颜色 */
}
legend {
font-size: 1.4em; /* Legend文字大小 */
color: #333; /* 文字颜色 */
}
</style>
</head>
<body>
@@ -21,7 +62,7 @@
src="//unpkg.com/outeres@0.0.10/img/layui/icon-v2.png"
class="layui-nav-img"
/>
{{ session.nickname }}
{{ session.name }}
</a>
<dl class="layui-nav-child">
<dd><a href="/home/profile">资料</a></dd> <!-- 修改这里的href指向/profile -->

View File

@@ -91,32 +91,11 @@
</div>
</div>
<div class="layui-body">
<div>
<!-- 动态显示当前时间的标题 -->
<h2 id="attendance-reminder">签到提醒 </h2>
<h2 id="current-time">当前时间</h2>
<table class="layui-table calendar-table" id="calendar">
<!-- 日历的头部 -->
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<!-- 日历的主体部分 -->
<tbody id="calendar-body">
<!-- 动态生成日历的日期部分 -->
</tbody>
</table>
<!-- 提醒内容 -->
<div class="reminder">
<span><span class="color-box" style="background-color: green;"></span>绿色代表当月已签到</span><br>
<span><span class="color-box" style="background-color: #FFA07A;"></span>颜色代表本月需要签到</span>
<div class="layui-container">
<div class="layui-row">
</div>
<div id="course-list" class="layui-row">
<!-- 课程列表将在这里生成 -->
</div>
</div>
</div>
@@ -127,59 +106,34 @@
<script src="/static/js/menu.js"></script>
<script src="/static/js/logout.js"></script>
<script>
layui.use(['layer'], function () {
var layer = layui.layer;
document.addEventListener("DOMContentLoaded", function () {
// 模拟从后端获取的课程数据
let courses = [
{name: "线性代数", time: "14:00", description: "线性代数是数学的一个基础分支,关注线性方程组、向量空间、矩阵理论及线性变换等概念。本课程将带领学生探索向量和矩阵的运算,理解行列式、特征值和特征向量的计算,以及线性空间和子空间的概念。透过一系列的应用实例,如图像处理和机器学习,学生将学会如何将理论应用于实际问题中,为进一步学习高等数学和工程应用打下坚实的基础。", room: "101教室"},
{name: "数据结构", time: "15:00", description: "数据结构是计算机科学中的一个重要领域,涉及组织、管理和存储数据的方式,以便高效地访问和修改。本课程覆盖了从基本的数据结构如数组、链表、栈和队列,到更高级的结构如树、图、散列表和堆。通过理论学习与实际编程相结合的方式,学生将掌握如何选择合适的数据结构解决特定问题,以及对各种数据结构进行效率分析和比较。", room: "102教室"},
{name: "计算机网络", time: "16:00", description: "计算机网络是研究计算机之间的连接方式及其通信协议的科学。本课程提供计算机网络的综合介绍包括网络架构、协议、网络通信理论、实际应用等内容。学生将了解网络层次结构包括物理层、数据链路层、网络层、传输层和应用层。课程重点讲解TCP/IP模型以及它在网络通信中的关键作用。通过本课程的学习学生将获得设计、实施、管理和维护网络的基本知识和技能。", room: "103教室"}
];
// 假设的已签到日期数据
var signedDays = [1, 5, 9]; // 这里仅为示例,实际应从服务器获取
var requiredSignDays = [2, 6, 15, 23];
// 获取课程列表容器
let courseListDiv = document.getElementById('course-list');
// 动态生成日历
function generateCalendar() {
var today = new Date();
var currentMonth = today.getMonth();
var currentYear = today.getFullYear();
var firstDay = new Date(currentYear, currentMonth, 1).getDay();
var daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate();
var calendarHtml = '';
var day = 1;
for (var i = 0; i < 6; i++) {
calendarHtml += '<tr>';
for (var j = 0; j < 7; j++) {
if (i === 0 && j < firstDay) {
calendarHtml += '<td></td>';
} else if (day > daysInMonth) {
break;
} else {
var classes = '';
if (signedDays.includes(day)) classes += 'signed '; // 已签到
if (requiredSignDays.includes(day)) classes += 'required-sign'; // 需要签到
calendarHtml += `<td class="${classes}">${day}</td>`;
day++;
}
}
calendarHtml += '</tr>';
if (day > daysInMonth) {
break;
}
}
document.getElementById('calendar-body').innerHTML = calendarHtml;
}
function updateTime() {
var now = new Date();
var timeStr = now.getFullYear() + '年' + (now.getMonth() + 1) + '月' + now.getDate() + '日';
document.getElementById('current-time').innerText = '当前时间: ' + timeStr;
}
// 页面加载时生成日历
generateCalendar();
updateTime();
// 遍历课程数组,为每门课创建一个卡片
courses.forEach(course => {
let courseDiv = document.createElement('div');
courseDiv.className = 'layui-col-md4';
courseDiv.innerHTML = `<div class="course-card layui-elem-field">
<legend>${course.name}</legend>
<div class="layui-field-box">
<p><strong>时间:</strong> ${course.time}</p>
<p><strong>教室:</strong> ${course.room}</p>
<p><strong>描述:</strong> ${course.description}</p>
<p><strong>剩余时间:</strong> <span class="countdown">计算中...</span></p>
</div>
</div>`;
courseListDiv.appendChild(courseDiv);
});
});
</script>
</body>
</html>

View File

@@ -7,6 +7,40 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link href="static/css/layui.css" rel="stylesheet">
<style>
#sign-in-reminder {
max-width: 600px; /* 限制最大宽度 */
margin: 50px auto; /* 上下保留空间,左右自动以居中显示 */
padding: 20px; /* 内边距 */
border-radius: 8px; /* 轻微的圆角 */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 给予阴影以突出层次 */
background-color: #f7f7f7; /* 浅灰色背景 */
}
#title {
text-align: center; /* 标题居中 */
color: #333; /* 标题颜色 */
font-size: 24px; /* 字号大小 */
}
#course-info {
text-align: center; /* 课程信息文本居中 */
margin-bottom: 20px; /* 与下方元素保持距离 */
}
.layui-btn {
width: 100%; /* 使按钮宽度填满容器 */
background-color: #009688; /* 设定一个现代感的按钮颜色 */
color: white; /* 文字颜色为白色 */
border-radius: 4px; /* 轻微的圆角 */
padding: 10px 0; /* 上下填充,增加按钮触摸面积 */
text-align: center; /* 文字水平居中 */
display: block; /* 转换为块级元素以应用宽度 */
line-height: 1.5; /* 调整行高以垂直居中文字 */
font-size: 16px; /* 设定文字大小 */
}
</style>
</head>
<body>
@@ -44,7 +78,7 @@
</div>
<div class="layui-body">
<div id="sign-in-reminder" class="layui-container">
<blockquote class="layui-elem-quote layui-text" id="title">
<blockquote class="layui-card-header" id="title">
课程签到
</blockquote>
<div class="layui-text" id="course-info"></div>
@@ -86,7 +120,7 @@
$("#sign-in-btn").click(function () {
if (!$(this).prop('disabled')) {
// 发送签到请求到后端
$.post("/api/student-sign-in",courseData,function (response) {
$.post("/api/student-sign-in", courseData, function (response) {
// 处理签到后的响应
if (response.msg === 'ok') {
layer.msg('签到成功!');

View File

@@ -9,9 +9,10 @@
<link href="static/css/layui.css" rel="stylesheet"/>
</head>
<body>
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<div class="layui-header layui-bg-black">
<div class="layui-logo layui-hide-xs layui-bg-black">网上上课点名系统</div>
<!-- 头部区域可配合layui 已有的水平导航) -->
<ul class="layui-nav layui-layout-right">
@@ -82,7 +83,7 @@
data: tableData // 使用处理后的数据
});
});
});
});
</script>
</body>
</html>

View File

@@ -7,6 +7,34 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link href="static/css/layui.css" rel="stylesheet"/>
<style>
.custom-table {
border-collapse: collapse;
width: 100%;
border: 1px solid #ddd; /* 轻灰色边框 */
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 轻微的阴影 */
}
.custom-table th, .custom-table td {
text-align: left;
padding: 12px 15px;
border-bottom: 1px solid #ddd; /* 每行之间的分割线 */
}
.custom-table th {
background-color: #f2f2f2; /* 轻灰色背景 */
color: #333; /* 深色文字 */
}
.custom-table tr:hover {
background-color: #f5f5f5; /* 鼠标悬浮时的背景色 */
}
.custom-table td {
color: #555; /* 内容的文字颜色 */
}
</style>
</head>
<body>
@@ -45,27 +73,21 @@
<div class="layui-body">
<!-- 内容主体区域 -->
<div style="padding: 15px;">
<blockquote class="layui-elem-quote layui-text">
课程信息
</blockquote>
<table class="layui-table">
<table class="layui-table custom-table">
<!-- 略去colgroup中课程代码的列 -->
<colgroup>
<col width="200">
<col width="150">
<col width="150">
<col width="150">
<col width="100">
<col>
<col width="200">
</colgroup>
<thead>
<tr>
<th>课程名称</th>
<th>课程代码</th>
<th>学分</th>
<th>课程描述</th>
</tr>
</thead>
<tbody>
<!-- 动态生成的课程信息将填充在这里 -->
</tbody>
</table>
</div>
@@ -77,6 +99,7 @@
<script src="static/layui.js"></script>
<script src="/static/js/menu.js"></script>
<script src="/static/js/logout.js"></script>
<script>
$.get('/api/get-course-info', function (courses) {
var tbody = $('.layui-table tbody');
@@ -84,13 +107,14 @@
courses.forEach(function (course) {
var row = '<tr>' +
'<td>' + course.course_name + '</td>' +
'<td>' + course.course_code + '</td>' +
// 移除课程代码的数据
'<td>' + course.credits + '</td>' +
'<td>' + course.description + '</td>' +
'</tr>';
tbody.append(row); // 将新行添加到表格中
});
});
}
);
</script>
</body>
</html>

View File

@@ -9,11 +9,22 @@
<body class="layui-padding-3">
<style>
.demo-login-container{width: 320px; margin: 21px auto 0;}
.demo-login-other .layui-icon{position: relative; display: inline-block; margin: 0 2px; top: 2px; font-size: 26px;}
.demo-login-container {
width: 320px;
margin: 21px auto 0;
}
.demo-login-other .layui-icon {
position: relative;
display: inline-block;
margin: 0 2px;
top: 2px;
font-size: 26px;
}
</style>
<form class="layui-form" id="loginForm">
<h1 style="text-align:center; color: #16baaa;">网上上课点名系统</h1>
<h1 style="text-align:center; color: #1E90FF; font-family: 'Arial', sans-serif; text-shadow: 2px 2px 4px;">
网上上课点名系统</h1>
<div class="demo-login-container">
<div class="layui-form-item">
<div class="layui-input-wrap">
@@ -39,13 +50,17 @@
</div>
<div class="layui-form-item">
<!-- 登录按钮 -->
<button class="layui-btn layui-btn-fluid" type="submit" lay-submit lay-filter="login" id="btnLogin">登录</button>
<button class="layui-btn layui-btn-fluid" type="submit" lay-submit lay-filter="login" id="btnLogin"
style="background-color: #4CAF50; color: white;">登录
</button>
</div>
<div class="layui-form-item">
<!-- 注册按钮 -->
<button type="button" class="layui-btn layui-btn-fluid" id="btnRegister">注册</button>
<button type="button" class="layui-btn layui-btn-fluid" id="btnRegister"
style="background-color: #1E90FF; color: white;">注册
</button>
</div>
</div>
</form>
@@ -64,6 +79,7 @@
data: data.field, // 表单数据
dataType: 'json',
success: function (response) {
console.log(response)
if (response.success) {
window.location.href = '/attendance-reminder'; // 或者你的成功页面
} else {

View File

@@ -18,7 +18,7 @@
}
</style>
<h1 style="text-align:center; color: #16baaa;">注册</h1>
<h1 style="text-align:center; color: #1E90FF;">注册</h1>
<form class="layui-form">
<div class="demo-reg-container">
@@ -60,7 +60,8 @@
</div>
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" type="submit" lay-submit lay-filter="btnRegister">注册</button>
<button class="layui-btn layui-btn-fluid" type="submit" lay-submit lay-filter="btnRegister" style="background-color: #1E90FF; color: white;">注册</button>
</div>
</div>
</form>

76
app/templates/test.html Normal file
View File

@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>签到提醒仪表板</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/layui-src/dist/css/layui.css" media="all">
<style>
.course-card {
padding: 10px;
border: 1px solid #DDD;
border-radius: 5px;
margin: 10px 0;
background-color: #FFF;
}
.course-card .layui-field-box {
padding: 10px;
}
.course-card button {
width: 100%;
text-align: center;
}
</style>
</head>
<body>
<div class="layui-container">
<div class="layui-row">
<div class="layui-col-xs12">
<h1>签到提醒仪表板</h1>
</div>
</div>
<div id="course-list" class="layui-row">
<!-- 课程列表将在这里生成 -->
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/layui-src/dist/layui.all.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
// 模拟从后端获取的课程数据
let courses = [
{name: "数学", time: "14:00", description: "关于几何和代数的深入学习", teacher: "张老师", room: "101教室"},
{name: "物理", time: "15:00", description: "探索物质世界的基本规律", teacher: "李老师", room: "102教室"},
{name: "化学", time: "16:00", description: "化合物与元素的神秘世界", teacher: "王老师", room: "103教室"}
];
// 获取课程列表容器
let courseListDiv = document.getElementById('course-list');
// 遍历课程数组,为每门课创建一个卡片
courses.forEach(course => {
let courseDiv = document.createElement('div');
courseDiv.className = 'layui-col-md4';
courseDiv.innerHTML = `<div class="course-card layui-elem-field">
<legend>${course.name} - ${course.teacher}</legend>
<div class="layui-field-box">
<p><strong>时间:</strong> ${course.time}</p>
<p><strong>教室:</strong> ${course.room}</p>
<p><strong>描述:</strong> ${course.description}</p>
<p><strong>剩余时间:</strong> <span class="countdown">计算中...</span></p>
<button class="layui-btn layui-btn-normal">签到</button>
</div>
</div>`;
courseListDiv.appendChild(courseDiv);
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,43 @@
import unittest
import os
from app.views import *
from werkzeug.datastructures import FileStorage # 用于创建测试文件
class ReceiveExcelTestCase(unittest.TestCase):
def setUp(self):
app.testing = True # 设置 Flask 应用为测试模式
self.client = app.test_client()
# 创建一个测试用的Excel文件或使用一个真实的Excel文件路径
self.test_file_path = 'template.xlsx'
def test_receive_excel_no_file(self):
# 测试没有文件时的情况
response = self.client.post('/api/receive-excel')
self.assertEqual(response.status_code, 400)
self.assertEqual(response.get_json(), {"error": "No file part"})
def test_receive_excel_with_file(self):
# 模拟已登录用户
with self.client.session_transaction() as sess:
sess['number'] = 'X202301000001'
# 使用 FileStorage 创建测试文件对象
data = {
'file': (open(self.test_file_path, 'rb'), self.test_file_path)
}
# 发送 POST 请求到 /api/receive-excel
response = self.client.post('/api/receive-excel', data=data, content_type='multipart/form-data')
# 验证返回的状态码和响应
self.assertEqual(response.status_code, 200)
self.assertEqual(response.get_json(), {"message": "File successfully processed"})
def tearDown(self):
# 清理测试用的文件或数据
if os.path.exists(self.test_file_path):
os.remove(self.test_file_path)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,39 @@
import unittest
from app.views import *
from unittest.mock import patch
from flask import session
class StudentSignInTestCase(unittest.TestCase):
def setUp(self):
# 设置 Flask 测试模式
app.testing = True
self.client = app.test_client()
@patch('db.database_manager.DatabaseManager.update_sign_in_info')
def test_student_sign_in(self, mock_update_sign_in_info):
# 模拟已登录用户
with self.client.session_transaction() as sess:
sess['number'] = 'X202301000001'
# 模拟数据库操作
mock_update_sign_in_info.return_value = 1 # 假设成功更新签到信息
# 发送 POST 请求到/api/student-sign-in
response = self.client.post('/api/student-sign-in', data={
'course_name': '计算机导论',
'course_id': '2',
'student_number': 'X202301000001',
})
# 验证返回的状态码和JSON数据
self.assertEqual(response.status_code, 200)
json_data = response.get_json()
self.assertEqual(json_data, {"msg": "ok", "data": "签到成功!"})
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,37 @@
import unittest
from app.views import *
from unittest.mock import patch
from flask import session
class TeacherSignInTestCase(unittest.TestCase):
def setUp(self):
# 设置 Flask 测试模式
app.testing = True
self.client = app.test_client()
@patch('db.database_manager.DatabaseManager.update_sign_in_info')
def test_teacher_sign_in(self, mock_teacher_sign_in):
# 模拟已登录用户
with self.client.session_transaction() as sess:
sess['number'] = 'X202301000001'
# 模拟数据库操作的返回值
mock_teacher_sign_in.return_value = {"msg": "当前班级专业没有学生'"}
# 发送 POST 请求到/api/teacher-sign-in
response = self.client.post('/api/teacher-sign-in', data={
'course_id': '123',
'course_name': 'Test Course',
'class_name': 'TestClass',
'major_id': '456'
})
# 验证返回的状态码和数据
self.assertEqual(response.status_code, 200)
json_data = response.get_json()
self.assertEqual(json_data, {"msg": "当前班级专业没有学生"})
if __name__ == '__main__':
unittest.main()

73
app/tests/TestGetHtml.py Normal file
View File

@@ -0,0 +1,73 @@
import unittest
from app.views import *
class HtmlTestCase(unittest.TestCase):
def setUp(self):
app.testing = True # 设置 Flask 应用为测试模式
self.client = app.test_client() # 创建一个测试客户端
def test_get_course_info(self):
# 测试 GET 请求
response = self.client.get('/course-info')
# 验证响应状态码
self.assertEqual(response.status_code, 200)
# 验证响应的Content-Type头部值
self.assertIn('text/html', response.content_type)
def test_get_attendance(self):
# 测试 GET 请求
response = self.client.get('/attendance')
# 验证响应状态码
self.assertEqual(response.status_code, 200)
# 验证响应的Content-Type头部值
self.assertIn('text/html', response.content_type)
def test_get_announcement(self):
# 测试 GET 请求
response = self.client.get('/announcement')
# 验证响应状态码
self.assertEqual(response.status_code, 200)
# 验证响应的Content-Type头部值
self.assertIn('text/html', response.content_type)
def test_get_attendance_reminder(self):
# 测试 GET 请求
response = self.client.get('/attendance-reminder')
# 验证响应状态码
self.assertEqual(response.status_code, 200)
# 验证响应的Content-Type头部值
self.assertIn('text/html', response.content_type)
def test_get_course_category(self):
# 测试 GET 请求
response = self.client.get('/course-category')
# 验证响应状态码
self.assertEqual(response.status_code, 200)
# 验证响应的Content-Type头部值
self.assertIn('text/html', response.content_type)
def test_get_attendance_teacher(self):
# 测试 GET 请求
response = self.client.get('/attendance-teacher')
# 验证响应状态码
self.assertEqual(response.status_code, 200)
# 验证响应的Content-Type头部值
self.assertIn('text/html', response.content_type)
if __name__ == '__main__':
unittest.main()

44
app/tests/TestGetMenu.py Normal file
View File

@@ -0,0 +1,44 @@
import unittest
from app.views import *
from flask import session
from unittest.mock import patch # 如果需要模拟数据库方法
class MenuAPITestCase(unittest.TestCase):
def setUp(self):
app.testing = True # 设置 Flask 应用为测试模式
self.client = app.test_client() # 创建一个测试客户端
self.expected_menu = [
{"name": "课程类别", "path": "/course-category"},
{"name": "课程信息", "path": "/course-info"},
{"name": "课程签到", "path": "/attendance-teacher"},
{"name": "签到提醒", "path": "/attendance-reminder"}
]
def test_menu_no_login(self):
# 测试未登录情况下请求API
response = self.client.get('/api/menu')
self.assertEqual(response.status_code, 401)
self.assertEqual(response.get_json(), [])
def test_login(self):
with self.client:
self.client.post('/login', data=dict(
number='G0001',
password='1'
))
# 确认登录后session中有数据
self.assertIn('role', session)
# 执行logout
response = self.client.get('/api/menu')
print(response.get_json())
# 确认响应是重定向到登录页面
self.assertEqual(response.status_code, 200)
self.assertEqual(self.expected_menu,response.get_json())
if __name__ == '__main__':
unittest.main()

45
app/tests/TestLogin.py Normal file
View File

@@ -0,0 +1,45 @@
import unittest
from app.views import *
from db.database_manager import DatabaseManager # 确保你可以导入DatabaseManager
class LoginTestCase(unittest.TestCase):
def setUp(self):
# 设置 Flask 测试模式
app.testing = True
self.client = app.test_client()
def test_login_get(self):
# 测试 GET 请求返回登录页面
response = self.client.get('/login')
self.assertEqual(response.status_code, 200)
self.assertIn('text/html', response.content_type)
def test_successful_login_post(self):
# 测试有效的登录 POST 请求
with self.client:
response = self.client.post('/login', data={
'number': 'G0001',
'password': '1'
})
# 根据你的应用逻辑调整断言
self.assertEqual(response.status_code, 200)
def test_invalid_login_post(self):
# 测试无效的登录 POST 请求
response = self.client.post('/login', data={
'number': 'admin',
'password': 'admin'
})
# 将返回的数据解析为JSON
json_data = response.get_json()
# 确认JSON响应中的值
self.assertEqual(response.status_code, 200)
self.assertFalse(json_data['success'])
self.assertEqual(json_data['message'], "无效的用户名或密码")
# 运行测试
if __name__ == '__main__':
unittest.main()

34
app/tests/TestLogout.py Normal file
View File

@@ -0,0 +1,34 @@
import unittest
from app.views import *
class LogoutTestCase(unittest.TestCase):
def setUp(self):
# 设置 Flask 测试模式
app.testing = True
self.client = app.test_client()
def test_logout(self):
# 先登录,确保 session 是设置的
with self.client:
self.client.post('/login', data=dict(
number='G0001',
password='1'
))
# 确认登录后session中有数据
self.assertIn('number', session)
# 执行logout
response = self.client.get('/logout')
# 确认 session 被清空
self.assertNotIn('number', session)
# 确认响应是重定向到登录页面
self.assertEqual(response.status_code, 302)
self.assertTrue(response.location.endswith('/login'))
if __name__ == '__main__':
unittest.main()

View File

@@ -89,7 +89,7 @@ def login():
db_manager = DatabaseManager()
result = db_manager.valid_login(number, password) # 获取验证结果
print(result)
# 确保用户已验证且活跃(未被禁用)
if result['valid'] and result['status'] == 1:
# 登录成功
@@ -97,7 +97,7 @@ def login():
session['role'] = check_identity(number)
session['name'] = result['name']
return jsonify(success=True, message="登录成功")
elif not result['status']:
elif not result.get('status'):
# 用户被禁用的情况
return jsonify(success=False, message="账户已被禁用")
else:
@@ -395,7 +395,7 @@ def teacher_sign_in():
print(f"course_id: {course_id},course_name: {course_name},class_name: {class_name},major_id: {major_id}")
db_manager = DatabaseManager()
data = db_manager.teacher_sign_in(course_id, course_name, class_name, major_id, date, status,teacher_number)
data = db_manager.teacher_sign_in(course_id, course_name, class_name, major_id, date, status, teacher_number)
return data

View File

@@ -77,7 +77,7 @@ class DatabaseManager:
return {'valid': True, 'status': status, 'name': name}
# 密码不匹配或用户不存在,返回登录失败
return {'valid': False}
return {'valid': False, 'status': True}
def get_menu(self, role):
sql = "SELECT menu_name,path FROM menu_items WHERE role=%s ORDER BY `order`"
@@ -178,13 +178,16 @@ WHERE
class_name = self.fetch(sql_student, (student_number,))[0]['class_name']
# 使用class_name和day_of_week从schedule表获取course_id
sql_schedule = "SELECT course_id, FROM schedule WHERE day_of_week = %s AND class_name = %s AND period_id = %s;"
sql_schedule = "SELECT course_id FROM schedule WHERE day_of_week = %s AND class_name = %s AND period_id = %s;"
course_id = self.fetch(sql_schedule, (day_of_week, class_name, period_id))
print(f"course_id: {course_id}")
id = course_id[0]['course_id']
print(f"id: {id}")
# 对于每一个course_id从course表中查询course_name
sql_course = "SELECT course_name FROM course WHERE course_id = %s;"
course_name = self.fetch(sql_course, (course_id['course_id'],))
data = {"course_name": course_name, "course_id": course_id}
course_name = self.fetch(sql_course, (id,))[0]['course_name']
print(f"course_name: {course_name}")
data = {"course_name": course_name, "course_id": id}
return data
def update_sign_in_info(self, student_number, course_id, course_name, date, status):

131
mysql.sql
View File

@@ -1,33 +1,3 @@
CREATE TABLE user
(
user_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
number VARCHAR(15) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
status BOOLEAN NOT NULL
);
INSERT INTO user (name, number, password, status) VALUES
('教师1','G001','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('教师2','G002','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('教师3','G003','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('教师4','G004','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生1','X202301000001','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生2','X202301000002','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生3','X202301000003','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生4','X202301000004','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生5','X202301000005','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生6','X202301000006','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生7','X202301000007','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生8','X202301000008','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生9','X202301000009','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生10','X2023010000010','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生11','X2023010000011','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生12','X2023010000012','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE);
CREATE TABLE menu_items
(
id INT AUTO_INCREMENT PRIMARY KEY,
@@ -42,11 +12,8 @@ INSERT INTO menu_items (menu_name, role, path, `order`)
VALUES ('课程信息', 'student', '/course-info', 1),
('课程签到', 'student', '/attendance', 2),
('公告信息', 'student', '/announcement', 3),
('签到提醒', 'student', '/attendance-reminder', 4);
INSERT INTO menu_items (menu_name, role, path, `order`)
VALUES ('课程类别', 'teacher', '/course-category', 1),
('签到提醒', 'student', '/attendance-reminder', 4),
('课程类别', 'teacher', '/course-category', 1),
('课程信息', 'teacher', '/course-info', 2),
('课程签到', 'teacher', '/attendance-teacher', 3),
('签到提醒', 'teacher', '/attendance-reminder', 4);
@@ -94,7 +61,6 @@ VALUES ('大学计算机基础', 'CF001', '必修', 3, '介绍计算机基础知
('离散数学导论', 'IDTM01', '必修', 3, '介绍离散数学的基础知识和应用'),
('计算机网络', 'CN002', '必修', 4, '学习计算机网络的基础理论和协议');
CREATE TABLE attendance_record
(
record_id INT AUTO_INCREMENT PRIMARY KEY,
@@ -105,6 +71,61 @@ CREATE TABLE attendance_record
status VARCHAR(20)
);
CREATE TABLE time_period
(
period_id INT AUTO_INCREMENT UNIQUE,
period_name VARCHAR(10),
start_time TIME,
end_time TIME,
PRIMARY KEY (period_id)
);
INSERT INTO time_period (period_name, start_time, end_time)
VALUES ('一、二节', '08:00:00', '09:30:00'),
('三、四节', '10:00:00', '11:30:00'),
('五、六节', '14:30:00', '16:00:00'),
('七、八节', '16:30:00', '18:00:00');
CREATE TABLE class_student (
id INT AUTO_INCREMENT PRIMARY KEY,
teacher_number VARCHAR(20) NOT NULL,
class_name VARCHAR(20) NOT NULL , -- 班级
student_name VARCHAR(50) NOT NULL , -- 姓名
student_number VARCHAR(20) NOT NULL UNIQUE, -- 学号
course_id INT NOT NULL , -- 课程ID
course_name VARCHAR(100) NOT NULL , -- 课程名称
major_id VARCHAR(20) NOT NULL,
major VARCHAR(255) NOT NULL
);
CREATE TABLE user
(
user_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
number VARCHAR(15) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
status BOOLEAN NOT NULL
);
INSERT INTO user (name, number, password, status) VALUES
('教师1','G0001','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('教师2','G0002','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('教师3','G0003','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('教师4','G0004','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生1','X202301000001','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生2','X202301000002','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生3','X202301000003','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生4','X202301000004','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生5','X202301000005','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生6','X202301000006','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生7','X202301000007','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生8','X202301000008','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生9','X202301000009','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生10','X2023010000010','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生11','X2023010000011','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE),
('学生12','X2023010000012','$2b$12$COT85R.ice41B/ofAra2ZewTe1En3ZhF6CBKOv2WScTcy.jQAhEVO',TRUE);
CREATE TABLE major
(
@@ -157,11 +178,11 @@ CREATE TABLE teacher
FOREIGN KEY (user_id) REFERENCES user (user_id)
);
INSERT INTO teacher (name, user_id)
VALUES ('教师1', 1),
('教师2', 2),
('教师3', 3),
('教师4', 4);
INSERT INTO teacher (name, user_id,teacher_number)
VALUES ('教师1', 1,'G0001'),
('教师2', 2,'G0002'),
('教师3', 3,'G0003'),
('教师4', 4,'G0004');
CREATE TABLE major_course
(
@@ -262,32 +283,4 @@ INSERT INTO schedule (day_of_week, period_id, teacher_number, class_name, course
(5,3,'G0001','2023级02班',1),
(5,4,'G0001','2023级02班',3);
CREATE TABLE time_period
(
period_id INT AUTO_INCREMENT UNIQUE,
period_name VARCHAR(10),
start_time TIME,
end_time TIME,
PRIMARY KEY (period_id)
);
INSERT INTO time_period (period_name, start_time, end_time)
VALUES ('一、二节', '08:00:00', '09:30:00'),
('三、四节', '10:00:00', '11:30:00'),
('五、六节', '14:30:00', '16:00:00'),
('七、八节', '16:30:00', '18:00:00');
CREATE TABLE class_student (
id INT AUTO_INCREMENT PRIMARY KEY,
teacher_number VARCHAR(20) NOT NULL,
class_name VARCHAR(20) NOT NULL , -- 班级
student_name VARCHAR(50) NOT NULL , -- 姓名
student_number VARCHAR(20) NOT NULL UNIQUE, -- 学号
course_id INT NOT NULL , -- 课程ID
course_name VARCHAR(100) NOT NULL , -- 课程名称
major_id VARCHAR(20) NOT NULL,
major VARCHAR(255) NOT NULL
);

Binary file not shown.