Compare commits

...

221 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
c554d2d7af 创建 parse_table.py 2023-12-29 14:27:59 +08:00
9296b70ac3 更新 mysql.sql 2023-12-29 14:27:57 +08:00
cff2b66bc0 更新 template.xlsx 2023-12-29 14:27:54 +08:00
9d68073dd5 更新 database_manager.py 2023-12-29 14:27:52 +08:00
894d075d55 更新 views.py 2023-12-29 14:27:49 +08:00
8021f15e58 更新 import-class.html 2023-12-29 14:27:46 +08:00
b5461dbff4 更新 attendance-teacher.html 2023-12-29 14:27:44 +08:00
aab814a23a 更新 get_teacher_attendance_table.js 2023-12-29 14:27:38 +08:00
c0d0e43e73 创建 get_teacher_attendance_table.js 2023-12-29 11:34:34 +08:00
3e79a22956 更新 attendance-teacher.html 2023-12-29 11:34:31 +08:00
ca55d9d2fd 更新 views.py 2023-12-29 11:34:26 +08:00
e655b72a5c 更新 database_manager.py 2023-12-29 11:34:24 +08:00
e75cce4dc9 更新 mysql.sql 2023-12-29 11:34:18 +08:00
a2be2f6378 更新 announcement.html 2023-12-29 10:12:56 +08:00
b07880eb7d 创建 time_utils.py 2023-12-29 01:58:24 +08:00
9048134f55 创建 allowed_files.py 2023-12-29 01:58:22 +08:00
dc2aedb06b 创建 __init__.py 2023-12-29 01:58:19 +08:00
74d63734bc 更新 database_manager.py 2023-12-29 01:58:17 +08:00
70c0c7925d 更新 views.py 2023-12-29 01:58:15 +08:00
1dddf07b54 删除 time_utils.py 2023-12-29 01:58:12 +08:00
88f5713f16 删除 allowed_files.py 2023-12-29 01:58:10 +08:00
7075531fc6 更新 announcement.html 2023-12-29 01:58:02 +08:00
908fa9344b 更新 mysql.sql 2023-12-29 01:15:55 +08:00
519d77d5b2 更新 database_manager.py 2023-12-29 01:15:53 +08:00
d404ccfa78 更新 views.py 2023-12-29 01:15:51 +08:00
1deec07d33 更新 announcement.html 2023-12-29 01:15:49 +08:00
a00a85efdb 更新 database_manager.py 2023-12-29 00:47:59 +08:00
feb6d1e2ce 更新 views.py 2023-12-29 00:47:57 +08:00
548120f9db 更新 views.py 2023-12-29 00:21:54 +08:00
964332cf44 更新 mysql.sql 2023-12-29 00:21:30 +08:00
b5b5e92b73 更新 database_manager.py 2023-12-29 00:21:28 +08:00
f59b43c99e 更新 views.py 2023-12-29 00:21:26 +08:00
68a126cb8b 更新 attendance.html 2023-12-29 00:21:24 +08:00
dc66080c22 更新 mysql.sql 2023-12-28 23:32:11 +08:00
f69c0a112f 更新 database_manager.py 2023-12-28 23:32:08 +08:00
d41e846ee4 更新 views.py 2023-12-28 23:32:06 +08:00
c7024d5851 创建 time_utils.py 2023-12-28 23:32:03 +08:00
ada0a97a42 更新 attendance.html 2023-12-28 23:32:00 +08:00
fdd09d5e4d 更新 upload_excel.js 2023-12-28 23:31:52 +08:00
6ce370db9c 更新 mysql.sql 2023-12-28 20:14:39 +08:00
1edb119983 更新 course-category.html 2023-12-28 20:10:22 +08:00
ad9fcf4781 更新 attendance.html 2023-12-28 20:10:19 +08:00
7c13aa0e6c 更新 mysql.sql 2023-12-28 20:10:13 +08:00
8f5b0e7dd2 更新 database_manager.py 2023-12-28 19:27:22 +08:00
cffeebdec4 更新 import-class.html 2023-12-28 19:27:19 +08:00
22e3b797cb 更新 course-info.html 2023-12-28 19:27:14 +08:00
514d86e77c 创建 upload_excel.js 2023-12-28 19:27:03 +08:00
172bbc1be3 更新 views.py 2023-12-28 19:17:03 +08:00
1ebdfa44c8 创建 allowed_files.py 2023-12-28 19:17:01 +08:00
bb2104539c 更新 import-class.html 2023-12-28 19:16:58 +08:00
af9fd8af11 更新 .DS_Store 2023-12-28 18:54:02 +08:00
0cf81af5e9 更新 mysql.sql 2023-12-28 18:54:00 +08:00
8d91463dd0 创建 template.xlsx 2023-12-28 18:53:57 +08:00
d365c3c466 更新 database_manager.py 2023-12-28 18:53:55 +08:00
3e3d32c20e 更新 config.py 2023-12-28 18:53:52 +08:00
df2c025c63 更新 views.py 2023-12-28 18:53:48 +08:00
f1fac2037a 更新 import-class.html 2023-12-28 18:53:46 +08:00
ebd95f9413 更新 attendance-teacher.html 2023-12-28 18:53:44 +08:00
5fb451fa97 更新 .gitignore 2023-12-28 18:53:36 +08:00
c708aaf381 更新 .gitignore 2023-12-28 18:53:05 +08:00
0541e271f6 更新 database_manager.py 2023-12-28 17:50:47 +08:00
72758a4b95 更新 mysql.sql 2023-12-28 17:49:02 +08:00
0fde0df39c 更新 database_manager.py 2023-12-28 17:49:00 +08:00
e35abb4009 更新 views.py 2023-12-28 17:48:57 +08:00
a934ff1a80 更新 config.py 2023-12-28 17:08:33 +08:00
973140e324 更新 attendance-teacher.html 2023-12-28 17:02:17 +08:00
bc4c4b6d7c 更新 views.py 2023-12-28 17:02:14 +08:00
2ab12bf44e 更新 database_manager.py 2023-12-28 17:02:08 +08:00
035a072f9c 更新 attendance.html 2023-12-28 16:49:13 +08:00
ad9bda48cc 更新 course-category.html 2023-12-28 16:49:09 +08:00
c055ebe094 更新 course-info.html 2023-12-28 16:49:05 +08:00
22dfab9424 更新 views.py 2023-12-28 16:49:02 +08:00
5649e7e5a4 更新 mysql.sql 2023-12-28 16:48:31 +08:00
edba8b2b10 抽离获取菜单js代码 2023-12-28 16:40:41 +08:00
ba95a8e886 更新 mysql.sql 2023-12-28 16:34:38 +08:00
ca8cbadccc 更新 User.py 2023-12-28 16:34:35 +08:00
eaea8e1f0f 创建 Teacher.py 2023-12-28 16:34:33 +08:00
638dded5a1 创建 Student.py 2023-12-28 16:34:29 +08:00
95bd7c7e65 更新 database_manager.py 2023-12-28 16:34:27 +08:00
e11cddd05e 更新 views.py 2023-12-28 16:34:24 +08:00
1ed2e473e1 更新 register.html 2023-12-28 16:34:22 +08:00
3694a91c74 更新 profile.html 2023-12-28 16:34:20 +08:00
f3b0064dc7 更新 login.html 2023-12-28 16:34:17 +08:00
fca154ee23 更新 attendance.html 2023-12-28 16:34:15 +08:00
d7a82f6780 更新 attendance-reminder.html 2023-12-28 16:34:13 +08:00
1bb907c71e 更新 announcement.html 2023-12-28 16:34:10 +08:00
20bb7bd1f6 创建 menu.js 2023-12-28 16:34:08 +08:00
756e2ca96e 创建 logout.js 2023-12-28 16:34:05 +08:00
38c2e4ff18 更新 mysql.sql 2023-12-28 15:34:15 +08:00
c497dbb421 更新 database_manager.py 2023-12-27 18:59:29 +08:00
4527403846 更新 views.py 2023-12-27 18:59:26 +08:00
a5ffdc32e1 更新 course-info.html 2023-12-27 18:59:24 +08:00
d0d6679e5a 更新 course-category.html 2023-12-27 18:59:21 +08:00
c47b9e9450 更新 database_manager.py 2023-12-27 18:15:12 +08:00
a4c6ae3d0c 更新 mysql.sql 2023-12-27 18:15:09 +08:00
245d08d3cb 更新 login.html 2023-12-27 16:10:27 +08:00
4a176fb61f 更新 views.py 2023-12-27 16:10:24 +08:00
aa707d4999 更新 database_manager.py 2023-12-27 16:10:20 +08:00
f7f8e7d9f9 更新 User.py 2023-12-27 16:10:18 +08:00
c7a2d92d51 更新 mysql.sql 2023-12-27 16:10:14 +08:00
59dcdb8d13 删除 test.html 2023-12-27 11:33:54 +08:00
58e96145a3 更新 test.html 2023-12-27 11:33:43 +08:00
ae52b1869c 更新 attendance-reminder.html 2023-12-27 11:33:38 +08:00
7e274c5add 更新 announcement.html 2023-12-27 11:33:36 +08:00
1d7ec89b80 更新 requirements.txt 2023-12-27 00:20:43 +08:00
9e172002c3 创建 test.html 2023-12-26 21:10:26 +08:00
fd1596e7d4 更新 attendance.html 2023-12-26 21:10:24 +08:00
bf789cb32d 更新 attendance-reminder.html 2023-12-26 21:10:21 +08:00
24848e7791 更新 SmartRollCall.iml 2023-12-26 21:10:17 +08:00
1b89233330 更新 misc.xml 2023-12-26 21:10:12 +08:00
d9d336f200 更新 .gitignore 2023-12-26 21:09:56 +08:00
f7fb8bdece 更新 attendance-teacher.html 2023-12-26 19:18:03 +08:00
652c95bff9 更新 attendance-teacher.html 2023-12-26 19:08:04 +08:00
cddb55c014 更新 mysql.sql 2023-12-26 19:01:17 +08:00
0557cd15c8 更新 database_manager.py 2023-12-26 19:01:14 +08:00
5d66d17fe2 更新 views.py 2023-12-26 19:01:12 +08:00
8c79601d98 更新 import-class.html 2023-12-26 19:01:09 +08:00
8aff863916 更新 attendance-teacher.html 2023-12-26 19:01:06 +08:00
072a9d3359 更新 import-class.html 2023-12-26 14:58:54 +08:00
0d3a099690 更新 import-class.html 2023-12-26 14:57:18 +08:00
d22e97e3fc 更新 import-class.html 2023-12-26 14:54:35 +08:00
5f9559ae19 更新 mysql.sql 2023-12-26 14:54:00 +08:00
ef1f726fc6 更新 database_manager.py 2023-12-26 14:53:58 +08:00
a55b7efd20 更新 views.py 2023-12-26 14:53:55 +08:00
b7fac7ca42 创建 import-class.html 2023-12-26 14:53:53 +08:00
5f4be0635c 更新 course-info.html 2023-12-26 14:53:51 +08:00
2193e20ca8 创建 course-category.html 2023-12-26 14:53:48 +08:00
0668a1e4d0 更新 attendance.html 2023-12-26 14:53:46 +08:00
b6d7d17a36 创建 attendance-teacher.html 2023-12-26 14:53:43 +08:00
94a4d94bd1 更新 announcement.html 2023-12-26 14:53:40 +08:00
7e8cbf5aaa 更新 profile.html 2023-12-25 23:13:59 +08:00
eaeeba42ef 更新 home.html 2023-12-25 23:13:57 +08:00
429e1d637c 更新 course-info.html 2023-12-25 23:13:54 +08:00
8edb1cc618 更新 attendance.html 2023-12-25 23:13:51 +08:00
5387b3584b 更新 attendance-reminder.html 2023-12-25 23:13:49 +08:00
26dfd131f6 更新 announcement.html 2023-12-25 23:13:46 +08:00
892196a827 更新 mysql.sql 2023-12-25 23:02:18 +08:00
c73997452b 更新 database_manager.py 2023-12-25 23:02:15 +08:00
234d9cf5b8 更新 views.py 2023-12-25 23:02:13 +08:00
05e0e82736 更新 profile.html 2023-12-25 23:02:11 +08:00
9c4d5b8a7d 更新 home.html 2023-12-25 23:02:08 +08:00
a303a65156 创建 course-info.html 2023-12-25 23:02:06 +08:00
6487490ad7 创建 attendance.html 2023-12-25 23:02:04 +08:00
5915f30e08 创建 attendance-reminder.html 2023-12-25 23:02:02 +08:00
138e1a221e 创建 announcement.html 2023-12-25 23:01:58 +08:00
e172d78c30 更新 views.py 2023-12-25 21:59:58 +08:00
caefaa4472 更新 profile.html 2023-12-25 21:59:55 +08:00
6770e100ff 更新 home.html 2023-12-25 21:59:53 +08:00
08790e3e9e 更新 database_manager.py 2023-12-25 21:43:36 +08:00
6026eac50e 更新 views.py 2023-12-25 21:43:34 +08:00
29bce0557e 创建 profile.html 2023-12-25 21:43:31 +08:00
e3ac6e7839 更新 home.html 2023-12-25 21:43:29 +08:00
50dc888339 更新 mysql.sql 2023-12-25 20:40:43 +08:00
c180dad115 更新 database_manager.py 2023-12-25 20:40:41 +08:00
1511faecde 更新 views.py 2023-12-25 20:40:39 +08:00
8cda76cc65 更新 login.html 2023-12-25 20:40:37 +08:00
83350552a5 更新 home.html 2023-12-25 20:40:34 +08:00
01aa0afd3e 更新 mysql.sql 2023-12-25 17:35:48 +08:00
0f0aa99c0d 更新 views.py 2023-12-25 17:35:45 +08:00
f4eb448476 更新 home.html 2023-12-25 17:35:43 +08:00
f3ef70b096 更新 User.py 2023-12-25 16:43:50 +08:00
b863ac4c8c 更新 database_manager.py 2023-12-25 16:43:47 +08:00
ba6aa5fbb5 更新 connection.py 2023-12-25 16:43:42 +08:00
b47b0cb8b9 更新 config.py 2023-12-25 16:43:38 +08:00
f5e4386ba3 更新 views.py 2023-12-25 16:43:32 +08:00
bb53d199bd 更新 views.py 2023-12-25 15:09:42 +08:00
b9d18cbe17 更新 config.py 2023-12-25 15:09:40 +08:00
93ce602837 更新 login.html 2023-12-25 15:09:37 +08:00
e2efd8b029 更新 views.py 2023-12-25 15:02:15 +08:00
95b3aea9a2 更新 config.py 2023-12-25 15:02:12 +08:00
fc8569c81d 创建 __init__.py 2023-12-25 15:02:10 +08:00
a75ff25ba4 创建 connection.py 2023-12-25 15:02:06 +08:00
f0647238c4 创建 database_manager.py 2023-12-25 15:02:04 +08:00
7bf2cfb68a 创建 __init__.py 2023-12-25 15:02:02 +08:00
cfa14f34be 创建 User.py 2023-12-25 15:02:00 +08:00
4a045b7fc6 创建 mysql.sql 2023-12-25 15:01:57 +08:00
10bb0b8f77 更新 app.py 2023-12-25 15:01:52 +08:00
2def6b2353 更新 home.html 2023-12-23 17:56:32 +08:00
b94d91d8b5 更新 app.py 2023-12-23 17:56:29 +08:00
4bad46e43e 更新 app.py 2023-12-23 17:47:29 +08:00
80d7b89cfa 创建 home.html 2023-12-23 17:46:15 +08:00
a8bfa76a6c 更新 app.py 2023-12-23 17:46:13 +08:00
4c8d30da70 更新 app.py 2023-12-23 17:25:13 +08:00
97a56e0e2f 创建 forget.html 2023-12-23 17:25:11 +08:00
08ea4fb4e6 更新 login.html 2023-12-23 17:25:09 +08:00
a0af99031d 更新 app.py 2023-12-23 16:58:55 +08:00
eeba5e0dfa 更新 register.html 2023-12-23 16:58:53 +08:00
a30a412aff 更新 login.html 2023-12-23 16:16:48 +08:00
867d9b7a12 更新 SmartRollCall.iml 2023-12-23 16:16:44 +08:00
6eddd91e18 更新 .gitignore 2023-12-23 16:16:37 +08:00
d2080e3189 更新 app.py 2023-12-23 16:05:48 +08:00
9de6a25fd8 创建 register.html 2023-12-23 16:05:46 +08:00
5c909f8626 更新 login.html 2023-12-23 16:05:44 +08:00
1efc782862 创建 jquery.min.js 2023-12-23 16:04:29 +08:00
a17fd9e769 更新 SmartRollCall.iml 2023-12-23 16:04:14 +08:00
d8b069a66d 更新 .gitignore 2023-12-23 16:04:07 +08:00
47 changed files with 2909 additions and 61 deletions

BIN
.DS_Store vendored

Binary file not shown.

8
.gitignore vendored
View File

@@ -11,4 +11,10 @@ htmlcov/
dist/ dist/
build/ build/
*.egg-info/ *.egg-info/
.idea/
.idea/*
.idea/SmartRollCall.iml
*.DS_Store
.DS_Store

View File

@@ -7,14 +7,15 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" /> <excludeFolder url="file://$MODULE_DIR$/venv" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.11 (venv)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.11 (SmartRollCall)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="jquery" level="application" />
</component> </component>
<component name="TemplatesService"> <component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" /> <option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
<option name="TEMPLATE_FOLDERS"> <option name="TEMPLATE_FOLDERS">
<list> <list>
<option value="$MODULE_DIR$/../flaskProject/templates" /> <option value="$MODULE_DIR$/app/templates" />
</list> </list>
</option> </option>
</component> </component>

2
.idea/misc.xml generated
View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (venv)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (SmartRollCall)" project-jdk-type="Python SDK" />
</project> </project>

View File

@@ -1,11 +1,2 @@
from flask import Flask, render_template import views
app = Flask(__name__)
@app.route('/')
def login():
return render_template('login.html')
if __name__ == '__main__':
app.run()

File diff suppressed because one or more lines are too long

2
app/static/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,60 @@
layui.use(['laypage', 'element', 'jquery', 'dropdown'], function () {
var laypage = layui.laypage;
var $ = layui.jquery;
function renderTable(page) {
$.get('/api/get-teacher-attendance-table?page=' + page, function (response) {
var $tbody = $('#attendanceTable tbody');
$tbody.empty(); // 清空表格内容
response.data.forEach(function (item) {
var $row = $('<tr></tr>');
$row.append(`<td>${item.course_id}</td>`);
$row.append(`<td>${item.course_name}</td>`);
$row.append(`<td>${item.class_name}</td>`);
$row.append(`<td>${item.major}</td>`);
$row.append(`<td><div class="layui-btn-container">
<button type="button" class="layui-btn btn-sign-in"
data-course-id="${item.course_id}"
data-course-name="${item.course_name}"
data-class-name = "${item.class_name}"
data-major-id = "${item.major_id}"
>签到</button>
</div>
</td>`)
$tbody.append($row);
});
laypage.render({
elem: 'pagination',
count: response.count,
limit: 10,
curr: page,
jump: function (obj, first) {
if (!first) {
renderTable(obj.curr);
}
}
});
});
}
// 为动态生成的按钮添加点击事件
$(document).on('click', '.btn-sign-in', function () {
var courseId = $(this).data('course-id'); // 获取课程ID
var courseName = $(this).data('course-name'); // 获取课程名
var className = $(this).data('class-name'); // 获取课程名
var majorId = $(this).data('major-id'); // 获取课程名
// console.log(courseId,courseName,className,majorId)
// 向后端发送POST请求
$.post('/api/teacher-sign-in', {
course_id: courseId,
course_name: courseName,
class_name: className,
major_id:majorId
}, function (response) {
layer.msg(response.msg);
});
});
renderTable(1); // 初始加载第一页
});

9
app/static/js/logout.js Normal file
View File

@@ -0,0 +1,9 @@
var $ = layui.jquery; // 获取Layui的jQuery对象
$('#logoutLink').on('click', function () {
// 向后端发送登出请求
$.get('/logout', function (data) {
// 重定向到登录页面,或根据后端响应做其他处理
window.location.href = '/login';
});
});

11
app/static/js/menu.js Normal file
View File

@@ -0,0 +1,11 @@
$.get('/api/menu', function (menuItems) {
var menuList = $('.layui-nav.layui-nav-tree');
menuList.empty();
menuItems.forEach(function (item) {
var href = item.path || 'javascript:void(0);'; // 提供一个默认值
menuList.append('<li class="layui-nav-item"><a href="' + href + '">' + item.name + '</a></li>');
});
layui.element.render('nav', 'test');
});

View File

@@ -0,0 +1,45 @@
// 获取上传按钮和文件输入元素
var uploadBtn = document.getElementById('uploadExcel');
var fileInput = document.getElementById('excelFile');
// 当点击上传按钮时触发文件输入的点击事件
uploadBtn.addEventListener('click', function () {
fileInput.click();
});
// 处理文件选择事件
fileInput.addEventListener('change', function () {
var file = this.files[0]; // 获取文件对象
if (file) {
// 检查文件类型
var fileName = file.name;
var fileExt = fileName.split('.').pop().toLowerCase();
if (fileExt === 'xlsx' || fileExt === 'xls') {
// 使用 FormData 上传文件
var formData = new FormData();
formData.append('file', file, fileName); // 'file' 是你的服务器端期待的字段名
// 使用 fetch 发送文件
fetch('/api/receive-excel', {
method: 'POST',
body: formData // 传递表单数据
})
.then(response => {
if (response.ok) {
return response.json(); // 如果上传成功解析JSON响应
}
throw new Error('Network response was not ok.'); // 如果上传失败,抛出错误
})
.then(data => {
// 处理响应数据
layer.msg('上传成功!'); // 弹出成功消息
})
.catch(error => {
console.error('Upload failed:', error);
layer.msg("文件上传失败!"); // 弹出失败消息
});
} else {
alert("请上传Excel文件!");
}
}
});

View File

@@ -0,0 +1,129 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>公告信息</title>
<meta name="renderer" content="webkit"/>
<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>
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<div class="layui-logo layui-hide-xs layui-bg-black">网上上课点名系统</div>
<!-- 头部区域可配合layui 已有的水平导航) -->
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item layui-hide layui-show-sm-inline-block">
<a href="javascript:;">
<img
src="//unpkg.com/outeres@0.0.10/img/layui/icon-v2.png"
class="layui-nav-img"
/>
{{ session.name }}
</a>
<dl class="layui-nav-child">
<dd><a href="/home/profile">资料</a></dd> <!-- 修改这里的href指向/profile -->
<dd><a href="javascript:;" id="logoutLink">登出</a></dd>
</dl>
</li>
<li
class="layui-nav-item"
lay-header-event="menuRight"
lay-unselect
></li>
</ul>
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<!-- 动态加载菜单栏 -->
<ul class="layui-nav layui-nav-tree" lay-filter="test">
</ul>
</div>
</div>
<div class="layui-body">
<div class="layui-container">
<div class="layui-row">
<div class="layui-col-xs12">
<h1 class="layui-header">公告</h1>
</div>
</div>
<div class="layui-row">
<div class="layui-col-md6">
<fieldset class="layui-elem-field">
<legend>今日课程</legend>
<div class="layui-field-box" id="courses-container">
<!-- 课程信息将通过JavaScript动态添加 -->
</div>
</fieldset>
</div>
</div>
</div>
</div>
</div>
<script src="static/jquery.min.js"></script> <!-- 确保已经引入jQuery -->
<script src="static/layui.js"></script>
<script src="/static/js/menu.js"></script>
<script src="/static/js/logout.js"></script>
<script>
$(document).ready(function () {
// 请求后端获取课程信息
$.get('/api/get-today-courses', function (data) {
if (data.msg == "ok") {
data.data.forEach(function (course) {
// 为每个课程创建一个段落<p>并添加到容器中,并设置样式使字体更大且更加好看
$('#courses-container').append('<p style="font-size: 1.2em; margin: 5px 0;">课程: ' + course.course_name + ' <br> 时间: ' + course.time + '</p>');
});
} else {
$('#courses-container').append(data.msg)
}
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>签到提醒</title>
<meta name="renderer" content="webkit"/>
<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>
.calendar-table {
width: 100%;
text-align: center;
border-collapse: collapse;
}
.calendar-table td {
font-size: 20px; /* 增加日期的字体大小 */
}
.signed {
color: #fff; /* 白色文字 */
background-color: green; /* 已签到日期的背景色 */
}
#current-time {
color: #16baaa;
margin-bottom: 15px;
}
#attendance-reminder {
color: #16baaa;
margin-bottom: 15px;
}
.reminder {
margin-top: 20px;
font-size: 16px;
text-align: center;
}
.calendar-table th {
background-color: #f2f2f2;
}
.color-box {
display: inline-block;
width: 20px;
height: 20px;
vertical-align: middle;
margin-right: 5px;
}
.required-sign {
background-color: #FFA07A; /* 需要签到日期的背景色 */
}
</style>
</head>
<body>
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<div class="layui-logo layui-hide-xs layui-bg-black">网上上课点名系统</div>
<!-- 头部区域可配合layui 已有的水平导航) -->
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item layui-hide layui-show-sm-inline-block">
<a href="javascript:;">
<img
src="//unpkg.com/outeres@0.0.10/img/layui/icon-v2.png"
class="layui-nav-img"
/>
{{ session.name }}
</a>
<dl class="layui-nav-child">
<dd><a href="/home/profile">资料</a></dd> <!-- 修改这里的href指向/profile -->
<dd><a href="javascript:;" id="logoutLink">登出</a></dd>
</dl>
</li>
<li
class="layui-nav-item"
lay-header-event="menuRight"
lay-unselect
></li>
</ul>
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<!-- 动态加载菜单栏 -->
<ul class="layui-nav layui-nav-tree" lay-filter="test">
</ul>
</div>
</div>
<div class="layui-body">
<div class="layui-container">
<div class="layui-row">
</div>
<div id="course-list" class="layui-row">
<!-- 课程列表将在这里生成 -->
</div>
</div>
</div>
</div>
<script src="static/jquery.min.js"></script> <!-- 确保已经引入jQuery -->
<script src="static/layui.js"></script>
<script src="/static/js/menu.js"></script>
<script src="/static/js/logout.js"></script>
<script>
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教室"}
];
// 获取课程列表容器
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}</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

@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>课程签到</title>
<meta name="renderer" content="webkit"/>
<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">
</head>
<body>
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<div class="layui-logo layui-hide-xs layui-bg-black">网上上课点名系统</div>
<!-- 头部区域可配合layui 已有的水平导航) -->
<ul class="layui-nav layui-layout-left">
<!-- 移动端显示 -->
<li class="layui-nav-item layui-show-xs-inline-block layui-hide-sm" lay-header-event="menuLeft">
<i class="layui-icon layui-icon-spread-left"></i>
</li>
<li class="layui-nav-item layui-hide-xs"><a href="/attendance-teacher/attendance">签到</a></li>
<li class="layui-nav-item layui-hide-xs"><a href="/attendance-teacher/import-class">导入班级</a></li>
</ul>
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item layui-hide layui-show-sm-inline-block">
<a href="javascript:;">
<img
src="//unpkg.com/outeres@0.0.10/img/layui/icon-v2.png"
class="layui-nav-img"
/>
{{ session.name }}
</a>
<dl class="layui-nav-child">
<dd><a href="/home/profile">资料</a></dd> <!-- 修改这里的href指向/profile -->
<dd><a href="javascript:;" id="logoutLink">登出</a></dd>
</dl>
</li>
<li
class="layui-nav-item"
lay-header-event="menuRight"
lay-unselect
></li>
</ul>
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<!-- 动态加载菜单栏 -->
<ul class="layui-nav layui-nav-tree" lay-filter="test">
</ul>
</div>
</div>
<div class="layui-body">
<table id="attendanceTable" class="layui-table">
<!-- 表头 -->
<thead>
<tr>
<th>课程代码</th>
<th>课程名称</th>
<th>班级</th>
<th>专业</th>
<th>签到</th>
</tr>
</thead>
<tbody>
<!-- 表格数据将在这里填充 -->
</tbody>
</table>
<div id="pagination"></div>
</div>
</div>
<script src="/static/jquery.min.js"></script> <!-- 确保已经引入jQuery -->
<script src="/static/layui.js"></script>
<script src="/static/js/menu.js"></script>
<script src="/static/js/logout.js"></script>
<script src="/static/js/get_teacher_attendance_table.js"></script>
<script>
</script>
</body>
</html>

View File

@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>课程签到</title>
<meta name="renderer" content="webkit"/>
<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>
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<div class="layui-logo layui-hide-xs layui-bg-black">网上上课点名系统</div>
<!-- 头部区域可配合layui 已有的水平导航) -->
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item layui-hide layui-show-sm-inline-block">
<a href="javascript:;">
<img
src="//unpkg.com/outeres@0.0.10/img/layui/icon-v2.png"
class="layui-nav-img"
/>
{{ session.name }}
</a>
<dl class="layui-nav-child">
<dd><a href="/home/profile">资料</a></dd> <!-- 修改这里的href指向/profile -->
<dd><a href="javascript:;" id="logoutLink">登出</a></dd>
</dl>
</li>
<li
class="layui-nav-item"
lay-header-event="menuRight"
lay-unselect
></li>
</ul>
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<!-- 动态加载菜单栏 -->
<ul class="layui-nav layui-nav-tree" lay-filter="test">
</ul>
</div>
</div>
<div class="layui-body">
<div id="sign-in-reminder" class="layui-container">
<blockquote class="layui-card-header" id="title">
课程签到
</blockquote>
<div class="layui-text" id="course-info"></div>
<div class="layui-row" style="margin-top: 20px;">
<div class="layui-col-xs12">
<button class="layui-btn" id="sign-in-btn">立即签到</button>
</div>
</div>
</div>
</div>
</div>
<script src="static/jquery.min.js"></script> <!-- 确保已经引入jQuery -->
<script src="static/layui.js"></script>
<script src="/static/js/menu.js"></script>
<script src="/static/js/logout.js"></script>
<script>
$(document).ready(function () {
let courseData = {};
// 获取课程名称或状态
$.get("/api/get-course-name", function (response) {
if (response.msg === "ok") {
// 如果后端返回课程名
$("#course-info").text("课程:" + response.data.course_name + "。在上课时间内,请及时签到!");
// 启用签到按钮
$("#sign-in-btn").prop('disabled', false);
courseData = response.data;
} else {
// 根据不同的消息更新状态
$("#course-info").text(response.msg); // 显示没有课程的消息
// 禁用签到按钮
$("#sign-in-btn").prop('disabled', true);
}
});
// 绑定签到按钮事件
$("#sign-in-btn").click(function () {
if (!$(this).prop('disabled')) {
// 发送签到请求到后端
$.post("/api/student-sign-in", courseData, function (response) {
// 处理签到后的响应
if (response.msg === 'ok') {
layer.msg('签到成功!');
} else {
layer.msg(response.data);
}
});
} else {
layer.msg("当前不可签到"); // Or handle disabled button click as needed
}
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,89 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>课程类别</title>
<meta name="renderer" content="webkit"/>
<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"/>
</head>
<body>
<div class="layui-layout layui-layout-admin">
<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">
<li class="layui-nav-item layui-hide layui-show-sm-inline-block">
<a href="javascript:;">
<img
src="//unpkg.com/outeres@0.0.10/img/layui/icon-v2.png"
class="layui-nav-img"
/>
{{ session.name }}
</a>
<dl class="layui-nav-child">
<dd><a href="/home/profile">资料</a></dd> <!-- 修改这里的href指向/profile -->
<dd><a href="javascript:;" id="logoutLink">登出</a></dd>
</dl>
</li>
<li
class="layui-nav-item"
lay-header-event="menuRight"
lay-unselect
></li>
</ul>
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<!-- 动态加载菜单栏 -->
<ul class="layui-nav layui-nav-tree" lay-filter="test">
</ul>
</div>
</div>
<div class="layui-body">
<table id="courseTable" lay-filter="courseTableFilter"></table>
</div>
</div>
<script src="static/jquery.min.js"></script> <!-- 确保已经引入jQuery -->
<script src="static/layui.js"></script>
<script src="/static/js/menu.js"></script>
<script src="/static/js/logout.js"></script>
<script>
// 请求后端获取菜单数据
layui.use(['table', 'jquery'], function () {
var table = layui.table;
var $ = layui.jquery;
// 发起GET请求获取数据
$.get('/api/get-course-type', function (res) {
// 假设返回的res是一个对象包含必修和选修的课程名数组
// 处理返回的数据,转换为表格能接受的格式
var tableData = [];
res['必修'].forEach(function (course) {
tableData.push({course_type: '必修', course_name: course});
});
res['选修'].forEach(function (course) {
tableData.push({course_type: '选修', course_name: course});
});
// 渲染表格
table.render({
elem: '#courseTable',
cols: [[ // 设置表头
{field: 'course_type', title: '课程类型', sort: true},
{field: 'course_name', title: '课程名称', sort: true}
]],
data: tableData // 使用处理后的数据
});
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,120 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>课程信息</title>
<meta name="renderer" content="webkit"/>
<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>
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<div class="layui-logo layui-hide-xs layui-bg-black">网上上课点名系统</div>
<!-- 头部区域可配合layui 已有的水平导航) -->
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item layui-hide layui-show-sm-inline-block">
<a href="javascript:;">
<img
src="//unpkg.com/outeres@0.0.10/img/layui/icon-v2.png"
class="layui-nav-img"
/>
{{ session.name }}
</a>
<dl class="layui-nav-child">
<dd><a href="/home/profile">资料</a></dd> <!-- 修改这里的href指向/profile -->
<dd><a href="javascript:;" id="logoutLink">登出</a></dd>
</dl>
</li>
<li
class="layui-nav-item"
lay-header-event="menuRight"
lay-unselect
></li>
</ul>
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<!-- 动态加载菜单栏 -->
<ul class="layui-nav layui-nav-tree" lay-filter="test">
</ul>
</div>
</div>
<div class="layui-body">
<!-- 内容主体区域 -->
<div style="padding: 15px;">
<table class="layui-table custom-table">
<!-- 略去colgroup中课程代码的列 -->
<colgroup>
<col width="200">
<col width="150">
<col width="200">
</colgroup>
<thead>
<tr>
<th>课程名称</th>
<th>学分</th>
<th>课程描述</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
<script src="static/jquery.min.js"></script> <!-- 确保已经引入jQuery -->
<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');
tbody.empty(); // 清空表格现有内容
courses.forEach(function (course) {
var row = '<tr>' +
'<td>' + course.course_name + '</td>' +
// 移除课程代码的数据
'<td>' + course.credits + '</td>' +
'<td>' + course.description + '</td>' +
'</tr>';
tbody.append(row); // 将新行添加到表格中
});
}
);
</script>
</body>
</html>

91
app/templates/forget.html Normal file
View File

@@ -0,0 +1,91 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>重置</title>
<link rel="stylesheet" href="static/css/layui.css" media="all">
<style>
.layui-form {width: 450px; margin: 21px auto 0;}
</style>
</head>
<body>
<div class="layui-container">
<div class="layui-row">
<div class="layui-col-xs12 layui-col-sm12 layui-col-md12">
<form class="layui-form" style="margin-top: 20px;">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">验证手机号</label>
<div class="layui-input-inline layui-input-wrap">
<input type="tel" name="phone" lay-verify="required|phone" autocomplete="off"
lay-reqtext="请填写手机号" lay-affix="clear" class="layui-input demo-phone">
</div>
<div class="layui-form-mid" style="padding: 0!important;">
<button type="button" class="layui-btn layui-btn-primary" lay-on="get-vercode">获取验证码
</button>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">验证码</label>
<div class="layui-input-inline layui-input-wrap">
<input type="text" name="vercode" lay-verify="required" autocomplete="off" lay-affix="clear"
class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">新密码</label>
<div class="layui-input-block">
<input type="password" name="newPassword" required lay-verify="required"
placeholder="请输入新密码"
autocomplete="off" class="layui-input" id="newPassword">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">确认密码</label>
<div class="layui-input-block">
<input type="password" name="confirmPassword" required lay-verify="required"
placeholder="请确认新密码"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="formDemo">重置密码</button>
</div>
</div>
</form>
</div>
</div>
</div>
<script src="static/layui.js"></script>
<script>
layui.use(['form'], function () {
var form = layui.form;
// 这里可以写一些表单验证逻辑
form.verify({
// 例如确认密码的逻辑
});
// 监听提交
form.on('submit(formDemo)', function (data) {
// 发送Ajax请求到后端API处理重置密码
// ...
return false; // 阻止表单跳转
});
});
</script>
</body>
</html>

54
app/templates/home.html Normal file
View File

@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>首页</title>
<meta name="renderer" content="webkit"/>
<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"/>
</head>
<body>
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<div class="layui-logo layui-hide-xs layui-bg-black">网上上课点名系统</div>
<!-- 头部区域可配合layui 已有的水平导航) -->
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item layui-hide layui-show-sm-inline-block">
<a href="javascript:;">
<img
src="//unpkg.com/outeres@0.0.10/img/layui/icon-v2.png"
class="layui-nav-img"
/>
{{ session.nickname }}
</a>
<dl class="layui-nav-child">
<dd><a href="/home/profile">资料</a></dd> <!-- 修改这里的href指向/profile -->
<dd><a href="javascript:;" id="logoutLink">登出</a></dd>
</dl>
</li>
<li
class="layui-nav-item"
lay-header-event="menuRight"
lay-unselect
></li>
</ul>
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<!-- 动态加载菜单栏 -->
<ul class="layui-nav layui-nav-tree" lay-filter="test">
</ul>
</div>
</div>
</div>
<script src="static/jquery.min.js"></script> <!-- 确保已经引入jQuery -->
<script src="static/layui.js"></script>
<script src="/static/js/menu.js"></script>
<script src="/static/js/logout.js"></script>
<script>
</script>
</body>
</html>

View File

@@ -0,0 +1,92 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>课程签到</title>
<meta name="renderer" content="webkit"/>
<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">
</head>
<body>
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<div class="layui-logo layui-hide-xs layui-bg-black">网上上课点名系统</div>
<!-- 头部区域可配合layui 已有的水平导航) -->
<ul class="layui-nav layui-layout-left">
<!-- 移动端显示 -->
<li class="layui-nav-item layui-show-xs-inline-block layui-hide-sm" lay-header-event="menuLeft">
<i class="layui-icon layui-icon-spread-left"></i>
</li>
<li class="layui-nav-item layui-hide-xs"><a href="/attendance-teacher/attendance">签到</a></li>
<li class="layui-nav-item layui-hide-xs"><a href="/attendance-teacher/import-class">导入班级</a></li>
</ul>
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item layui-hide layui-show-sm-inline-block">
<a href="javascript:;">
<img
src="//unpkg.com/outeres@0.0.10/img/layui/icon-v2.png"
class="layui-nav-img"
/>
{{ session.name }}
</a>
<dl class="layui-nav-child">
<dd><a href="/home/profile">资料</a></dd> <!-- 修改这里的href指向/profile -->
<dd><a href="javascript:;" id="logoutLink">登出</a></dd>
</dl>
</li>
<li
class="layui-nav-item"
lay-header-event="menuRight"
lay-unselect
></li>
</ul>
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<!-- 动态加载菜单栏 -->
<ul class="layui-nav layui-nav-tree" lay-filter="test">
</ul>
</div>
</div>
<div class="layui-body">
<!-- 内容主体区域 -->
<div style="margin: 20px;">
<div class="layui-row">
<div class="layui-col-md12">
<fieldset class="layui-elem-field">
<legend>学生信息上传 - 要求</legend>
<div class="layui-field-box">
<p>请按照以下格式准备Excel文件</p>
<ul>
<li>必须包含表头:班级、姓名、学号、课程、专业</li>
<li>确保数据格式正确无误</li>
</ul>
<p><b>注:</b>错误的数据格式可能导致上传失败</p>
</div>
</fieldset>
</div>
<div class="layui-col-md12" style="padding-top: 20px;">
<!-- 隐藏的文件输入,用户选择文件 -->
<input type="file" id="excelFile" style="display:none;" accept=".xlsx, .xls">
<!-- 上传按钮 -->
<button type="button" class="layui-btn" id="uploadExcel">上传Excel文件</button>
<!-- 下载模板链接 -->
<a href="/files/template.xlsx" class="layui-btn layui-btn-primary">下载模板</a>
</div>
</div>
</div>
</div>
</div>
<script src="/static/jquery.min.js"></script>
<script src="/static/layui.js"></script>
<script src="/static/js/menu.js"></script>
<script src="/static/js/logout.js"></script>
<script src="/static/js/upload_excel.js"></script>
<script>
</script>
</body>
</html>

View File

@@ -1,65 +1,104 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>登录</title> <title>登录</title>
<link href="static/css/layui.css" rel="stylesheet"> <link href="static/css/layui.css" rel="stylesheet">
</head> </head>
<body class="layui-padding-3"> <body class="layui-padding-3">
<style> <style>
.demo-login-container{width: 320px; margin: 21px auto 0;} .demo-login-container {
.demo-login-other .layui-icon{position: relative; display: inline-block; margin: 0 2px; top: 2px; font-size: 26px;} 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> </style>
<form class="layui-form"> <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;">
<div class="demo-login-container"> 网上上课点名系统</h1>
<div class="layui-form-item"> <div class="demo-login-container">
<div class="layui-input-wrap"> <div class="layui-form-item">
<div class="layui-input-prefix"> <div class="layui-input-wrap">
<i class="layui-icon layui-icon-username"></i> <div class="layui-input-prefix">
<i class="layui-icon layui-icon-username"></i>
</div>
<input type="text" name="number" value="" lay-verify="required" placeholder="学号/工号"
lay-reqtext="学号/工号" autocomplete="off" class="layui-input" lay-affix="clear">
</div>
</div> </div>
<input type="text" name="username" value="" lay-verify="required" placeholder="用户名" lay-reqtext="请填写用户名" autocomplete="off" class="layui-input" lay-affix="clear"> <div class="layui-form-item">
</div> <div class="layui-input-wrap">
</div> <div class="layui-input-prefix">
<div class="layui-form-item"> <i class="layui-icon layui-icon-password"></i>
<div class="layui-input-wrap"> </div>
<div class="layui-input-prefix"> <input type="password" name="password" value="" lay-verify="required" placeholder="密 码"
<i class="layui-icon layui-icon-password"></i> lay-reqtext="请填写密码" autocomplete="off" class="layui-input" lay-affix="eye">
</div>
</div> </div>
<input type="password" name="password" value="" lay-verify="required" placeholder="密 码" lay-reqtext="请填写密码" autocomplete="off" class="layui-input" lay-affix="eye"> <div class="layui-form-item">
</div> <input type="checkbox" name="remember" lay-skin="primary" title="记住密码">
<a href="forget" style="float: right; margin-top: 7px;">忘记密码?</a>
</div>
<div class="layui-form-item">
<!-- 登录按钮 -->
<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"
style="background-color: #1E90FF; color: white;">注册
</button>
</div>
</div> </div>
<div class="layui-form-item">
<input type="checkbox" name="remember" lay-skin="primary" title="记住密码">
<a href="#forget" style="float: right; margin-top: 7px;">忘记密码?</a>
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="demo-login">登录</button>
</div>
</div>
</form> </form>
<script src="static/layui.js"></script> <script src="static/layui.js"></script>
<script src="static/jquery.min.js"></script>
<script> <script>
layui.use(function(){ layui.use(['form', 'layer'], function () {
var form = layui.form; var form = layui.form;
var layer = layui.layer; var layer = layui.layer;
// 提交事件
form.on('submit(demo-login)', function(data){ // 监听提交事件
var field = data.field; // 获取表单字段值 form.on('submit(login)', function (data) {
// 显示填写结果,仅作演示用 $.ajax({
layer.alert(JSON.stringify(field), { type: "POST",
title: '当前填写的字段值' url: "/login", // 确保这是你的登录路由
data: data.field, // 表单数据
dataType: 'json',
success: function (response) {
console.log(response)
if (response.success) {
window.location.href = '/attendance-reminder'; // 或者你的成功页面
} else {
console.log(response.message)
layer.alert('登录失败: ' + response.message);
}
},
error: function () {
layer.alert('登录请求失败');
}
});
return false; // 阻止表单的默认提交
});
// 监听注册按钮的点击事件
$('#btnRegister').click(function () {
window.location.href = '/register'; // 确保这是你的注册路由
});
}); });
// 此处可执行 Ajax 等操作
// …
return false; // 阻止默认 form 跳转
});
});
</script> </script>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>资料</title>
<meta name="renderer" content="webkit"/>
<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"/>
</head>
<body>
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<div class="layui-logo layui-hide-xs layui-bg-black" href="/home">网上上课点名系统</div>
</div>
<div class="layui-body">
<!-- 内容主体区域 -->
<div style="padding: 15px">
<blockquote class="layui-elem-quote layui-text">
更换头像和昵称
</blockquote>
<!-- 更换头像和昵称的表单 -->
<form id="profileForm" method="post" enctype="multipart/form-data">
<div class="layui-form-item">
<label class="layui-form-label">头像:</label>
<div class="layui-input-block">
<input type="file" name="avatar"/>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" type="submit">更新</button>
</div>
</div>
</form>
</div>
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<!-- 动态加载菜单栏 -->
<ul class="layui-nav layui-nav-tree" lay-filter="test">
</ul>
</div>
</div>
</div>
<script src="/static/jquery.min.js"></script> <!-- 确保已经引入jQuery -->
<script src="/static/layui.js"></script>
<script src="/static/js/menu.js"></script>
<script>
</script>
</body>
</html>

152
app/templates/register.html Normal file
View File

@@ -0,0 +1,152 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>注册</title>
<link href="static/css/layui.css" rel="stylesheet">
</head>
<body>
<style>
.demo-reg-container {
width: 320px;
margin: 21px auto 0;
}
.demo-reg-item {
margin-bottom: 10px; /* 调整间距 */
}
</style>
<h1 style="text-align:center; color: #1E90FF;">注册</h1>
<form class="layui-form">
<div class="demo-reg-container">
<div class="layui-form-item demo-reg-item">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-username"></i>
</div>
<input type="text" name="name" value="" lay-verify="required" placeholder="姓名"
autocomplete="off" class="layui-input" lay-affix="clear">
</div>
</div>
<div class="layui-form-item demo-reg-item">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-cellphone"></i>
</div>
<input type="text" name="number" value="" placeholder="学号X开头/工号G开头"
lay-reqtext="请填写学号/工号" autocomplete="off" class="layui-input" id="reg-number"
lay-verify="customRule">
</div>
</div>
<div class="layui-form-item demo-reg-item">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-password"></i>
</div>
<input type="password" name="password" value="" lay-verify="required" placeholder="密码"
autocomplete="off" class="layui-input" id="reg-password" lay-affix="eye">
</div>
</div>
<div class="layui-form-item demo-reg-item">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-password"></i>
</div>
<input type="password" name="confirmPassword" value="" lay-verify="required|confirmPassword"
placeholder="确认密码" autocomplete="off" class="layui-input" lay-affix="eye">
</div>
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" type="submit" lay-submit lay-filter="btnRegister" style="background-color: #1E90FF; color: white;">注册</button>
</div>
</div>
</form>
<script src="static/layui.js"></script>
<script src="static/jquery.min.js"></script>
<script>
layui.use(function () {
var $ = layui.$;
var form = layui.form;
var layer = layui.layer;
var util = layui.util;
form.verify({
confirmPassword: function (value, item) {
var passwordValue = $('#reg-password').val();
if (value !== passwordValue) {
return '两次密码输入不一致';
}
},
customRule: function (value, item) {
// 首先检查是否以X或G开头
if (!value.startsWith('X') && !value.startsWith('G')) {
return '学号必须以X开头工号必须以G开头';
}
// 如果以X开头检查长度是否为13位
if (value.startsWith('X') && value.length !== 13) {
return '学号长度必须为13位';
}
// 如果以G开头检查长度是否为5位
if (value.startsWith('G') && value.length !== 5) {
return '工号长度必须为5位';
}
}
});
layui.use(['form', 'layer'], function () {
var form = layui.form;
var layer = layui.layer;
// 监听注册表单的提交事件
form.on('submit(btnRegister)', function (data) {
// 构建要提交的数据对象
var postData = {
name: data.field.name, // 昵称
number: data.field.number, // 手机号
password: data.field.password, // 密码
};
// 发送AJAX POST请求
console.log("Sending AJAX request to /register", postData);
$.ajax({
type: "POST",
url: "/register", // 修改为你的注册接口
contentType: "application/json",
data: JSON.stringify(postData), // 将数据转为JSON字符串
dataType: 'json',
success: function (response) {
// 根据返回的结果进行操作
if (response.success) {
layer.msg('注册成功!', {
time: 2000 //2秒关闭如果不配置默认是3秒
}, function () {
// 关闭后执行的回调函数
window.location.href = '/';
});
} else {
// 显示错误信息
layer.alert('注册失败: ' + response.message);
}
},
error: function () {
layer.alert('注册请求失败');
}
});
return false; // 阻止表单的默认提交
});
});
});
</script>
</body>
</html>
xw

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

@@ -0,0 +1,403 @@
import os
import logging
# 第三方库
import openpyxl
from flask import Flask, redirect, url_for, render_template, session, jsonify, request, send_file
from datetime import datetime
# 应用内部模块
from utils.time_utils import check_now_time
from utils.parse_table import parse_table
from utils.allowed_files import allowed_excel
from db.connection import MySQLPool
from db.database_manager import DatabaseManager
from models.Student import Student
from models.Teacher import Teacher
from models.User import User
from config import SECRET_KEY, LOGGING_CONFIG, FILE_PATH
app = Flask(__name__, static_folder='static')
app.secret_key = SECRET_KEY # 从配置文件设置
logging.basicConfig(**LOGGING_CONFIG)
# 一个全局MySQLPool对象用于管理数据库连接
mysql_pool = MySQLPool()
# 配置文件路径,例如指向一个 'files' 目录
app.config['FILE_PATH'] = FILE_PATH
@app.route('/')
def index():
# 如果用户已登录,则重定向到主页;否则,重定向到登录页面
if 'number' in session:
return redirect(url_for('home'))
else:
return render_template('login.html')
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
data = request.get_json()
print(data)
user = User(
name=data.get('name'),
number=data.get('number'),
password=data.get('password'), # 确保密码安全处理
status=True
)
identity = check_identity(user.number)
db_manager = DatabaseManager()
if not db_manager.user_exists(user.number):
db_manager.insert_user(user)
user_id = db_manager.query_user_id(user.number)
if identity == "teacher":
teacher = Teacher(name=user.name, teacher_number=user.number, user_id=user_id)
db_manager.insert_teacher(teacher)
else: # assumed student
class_name = user.number[1:5] + "" + user.number[5:7] + ""
major_id = user.number[7:10]
student = Student(student_name=user.name, student_number=user.number, user_id=user_id,
major_id=major_id,
class_name=class_name)
db_manager.insert_student(student)
return jsonify({"success": True, "message": "注册成功"})
else:
return jsonify({"success": False, "message": "用户已存在"})
else:
return render_template('register.html')
def check_identity(number):
identity = None
if number[0] == 'G':
identity = "teacher"
return identity
else:
identity = "student"
return identity
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
else:
number = request.form['number']
password = request.form['password']
db_manager = DatabaseManager()
result = db_manager.valid_login(number, password) # 获取验证结果
print(result)
# 确保用户已验证且活跃(未被禁用)
if result['valid'] and result['status'] == 1:
# 登录成功
session['number'] = number
session['role'] = check_identity(number)
session['name'] = result['name']
return jsonify(success=True, message="登录成功")
elif not result.get('status'):
# 用户被禁用的情况
return jsonify(success=False, message="账户已被禁用")
else:
# 其他登录失败情况
return jsonify(success=False, message="无效的用户名或密码")
@app.route('/forget', methods=['GET', 'POST'])
def forget_page():
return render_template('forget.html')
@app.route('/home')
def home():
if 'number' in session:
return render_template('home.html')
else:
return redirect("login")
@app.route('/logout')
def logout():
# 清除session中的所有信息
session.clear()
# 返回一个响应,或者重定向到登录页面
return redirect('/login')
@app.route('/api/menu')
def get_menu():
db_manager = DatabaseManager()
# 从session中获取用户角色
if 'role' in session:
role = session['role']
menu_items = db_manager.get_menu(role)
# 转换菜单项为期望的格式并返回
formatted_menu_items = [
{"name": item['menu_name'], "path": item['path']} for item in menu_items
]
return jsonify(formatted_menu_items)
# 如果没有角色信息可能用户未登录或session过期
return jsonify([]), 401 # 未授权状态码
@app.route('/home/profile', methods=['GET', 'POST'])
def profile():
if request.method == 'POST':
# 从表单获取数据
nickname = request.form['nickname']
avatar = request.files['avatar']
# 处理头像和昵称更新逻辑
# ...
return "资料更新成功" # 或者重定向到其他页面
# 如果是GET请求显示表单页面
return render_template('profile.html') # 确保这里渲染的是包含上面表单的HTML页面
@app.route('/course-info', methods=['GET', 'POST'])
def course_info():
if request.method == "GET":
return render_template('course-info.html')
@app.route('/api/get-course-info', methods=['GET'])
def get_course_info():
student_number = session.get('number')
db_manager = DatabaseManager()
if check_identity(student_number) == "student":
course_data = db_manager.get_class_courses(student_number)
return jsonify(course_data)
else:
course_data = db_manager.get_all_courses()
return jsonify(course_data)
@app.route('/api/get-course-type', methods=['GET'])
def get_course_type():
db_manager = DatabaseManager()
course_data = db_manager.get_course_type()
# 创建存储必修和选修课程名称的字典
course_info = {"必修": [], "选修": []}
for course in course_data:
if course['course_type'] == '必修':
course_info["必修"].append(course['course_name'])
elif course['course_type'] == '选修':
course_info["选修"].append(course['course_name'])
return jsonify(course_info)
@app.route('/api/get-announcement-info', methods=['GET'])
def get_announcement_info():
db_manager = DatabaseManager()
announcement_info = db_manager.get_announcement_info()
return jsonify(course_info)
@app.route('/attendance', methods=['GET', 'POST'])
def course_checkin():
return render_template('attendance.html')
@app.route('/announcement', methods=['GET', 'POST'])
def announcement():
return render_template('announcement.html')
@app.route('/attendance-teacher', methods=['GET', 'POST'])
def announcement_teacher():
return render_template('attendance-teacher.html')
@app.route('/attendance-reminder', methods=['GET', 'POST'])
def attendance_reminder():
return render_template('attendance-reminder.html')
@app.route('/course-category', methods=['GET', 'POST'])
def course_category():
return render_template('course-category.html')
@app.route('/attendance-teacher/import-class', methods=['GET'])
def import_class():
return render_template('import-class.html')
@app.route('/attendance-teacher/attendance', methods=['GET'])
def teacher_attendance():
return render_template('attendance-teacher.html')
@app.route('/api/get-teacher-attendance-table', methods=['GET'])
def get_current_teacher_courses():
number = session.get('number')
# 获取分页参数
page = request.args.get('page', 1, type=int) # 如果没有提供,默认为第一页
limit = request.args.get('limit', 10, type=int) # 如果没有提供默认每页10条
# 获取所有课程数据
db_manager = DatabaseManager()
all_course_data = db_manager.get_current_teacher_courses(number)
print(all_course_data)
logging.info(f"all_course_data: {all_course_data}")
# 计算分页的起始和结束索引
start = (page - 1) * limit
end = start + limit
# 获取当前页的数据
current_page_data = all_course_data[start:end]
# 构建响应字典
response = {
'msg': 'ok' if current_page_data else 'no_data',
'count': len(all_course_data), # 数据的总数
'data': current_page_data # 当前页的课程信息列表
}
# 将查询结果转换为JSON格式并返回
return jsonify(response)
@app.route('/files/template.xlsx')
def download_template():
# 确保这个路径是你的文件实际所在的服务器路径
path = os.path.join(app.config['FILE_PATH'], "template.xlsx")
return send_file(path, as_attachment=True)
@app.route('/api/receive-excel', methods=['POST'])
def receive_excel():
number = session.get('number')
# 检查是否有文件在请求中
if 'file' not in request.files:
return jsonify({"error": "No file part"}), 400
file = request.files['file']
# 如果用户没有选择文件,浏览器也会提交一个空的文件名
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
# 检查文件是不是 Excel 文件
if file and allowed_excel(file.filename):
try:
# 使用 openpyxl 读取文件内容
data = parse_table(file, number)
db_manager = DatabaseManager()
result = db_manager.insert_into_class_student(data)
print(result)
return jsonify({"message": "File successfully processed"}), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
else:
return jsonify({"error": "Invalid file type"}), 400
@app.route('/api/get-course-name', methods=['GET'])
def get_course_name():
period_id, period_name = check_now_time() # 获取当前时间段信息
print(f"period_id: {period_id}, period_name: {period_name}")
# 如果当前不在任何时间段内
if period_id is None:
return jsonify({
'msg': period_name, # 返回不在课程时间段内的消息
'data': None
})
# 获取当前用户编号
number = session.get('number')
if not number:
return jsonify({"msg": "用户未登录或编号不可用", "data": None})
# 获取今天是星期几
now = datetime.now()
day_of_week = now.weekday() + 1
# 如果是周末
if not (1 <= day_of_week <= 5):
return jsonify({"msg": "周末没有课程", "data": None})
# 如果是工作日,获取课程信息
print(f"day of week: {day_of_week}")
db_manager = DatabaseManager()
data = db_manager.get_course_name(number, day_of_week, period_id)
print(data)
# 返回课程信息
return jsonify({
'msg': 'ok',
'data': data
})
@app.route('/api/student-sign-in', methods=['POST'])
def student_sign_in():
number = session.get("number")
course_name = request.form.get('course_name')
course_id = request.form.get('course_id')
now = datetime.now()
date = now.strftime("%Y年%m月%d%H:%M")
status = "出勤"
print(f"number: {number},course_name: {course_name},course_id: {course_id}")
db_manager = DatabaseManager()
result = db_manager.update_sign_in_info(number, course_id, course_name, date, status)
if result == 1:
return jsonify({"msg": "ok", "data": "签到成功!"})
else:
return jsonify({"msg": "fail", "data": "签到失败!"})
@app.route('/api/get-today-courses')
def student_get_today_courses():
number = session.get('number')
if not number:
return jsonify({"msg": "用户未登录或编号不可用", "data": None})
# 获取今天是星期几
now = datetime.now() # 获取当前时间
day_of_week = now.weekday() + 1
# 如果是周末
if not (1 <= day_of_week <= 5):
return jsonify({"msg": "周末没有课程", "data": None})
db_manager = DatabaseManager()
data = db_manager.student_get_today_courses(number, day_of_week)
# 返回课程信息
return jsonify({
'msg': 'ok',
'data': data
})
@app.route('/api/teacher-sign-in', methods=['POST'])
def teacher_sign_in():
teacher_number = session.get('number')
course_id = request.form['course_id']
course_name = request.form['course_name']
class_name = request.form['class_name']
major_id = request.form['major_id']
now = datetime.now()
date = now.strftime("%Y年%m月%d%H:%M")
status = "出勤"
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)
return data
if __name__ == '__main__':
app.run(debug=True)

View File

@@ -0,0 +1,25 @@
# config.py
import pymysql
import logging
import os
# 设置session SECRET_KEY
SECRET_KEY = 'sUNiJ7QPulxrbmZD'
# 配置日志
LOGGING_CONFIG = {
'level': logging.INFO,
'format': '%(asctime)s - %(levelname)s - [%(filename)s:%(funcName)s] - %(message)s'
}
# 配置文件下载路径
FILE_PATH = os.path.join(os.getcwd(), 'files')
# 数据库连接配置
DB_CONFIG = {
'host': '42.193.20.110',
'port': 8006, # 注意端口是数字,不是字符串
'user': 'test',
'password': 'X7gq9lbxqpDGbyCi',
'database': 'test_db',
'charset': 'utf8mb4',
'cursorclass': pymysql.cursors.DictCursor
}

22
db/connection.py Normal file
View File

@@ -0,0 +1,22 @@
import pymysql
from dbutils.pooled_db import PooledDB
from config import DB_CONFIG
class MySQLPool:
def __init__(self):
# 初始化时建立数据库连接池
self.pool = PooledDB(
creator=pymysql, # 使用链接数据库的模块
maxconnections=6, # 连接池允许的最大连接数
mincached=2, # 初始化时,连接池中至少创建的空闲的连接
maxcached=5, # 连接池中最多闲置的连接
blocking=True, # 连接池中如果没有可用连接后是否阻塞等待
maxusage=None, # 一个连接最多被重复使用的次数None表示无限制
setsession=[], # 开始会话前执行的命令列表
ping=0,
**DB_CONFIG
)
def get_connection(self):
# 从连接池中获取一个连接
return self.pool.connection()

299
db/database_manager.py Normal file
View File

@@ -0,0 +1,299 @@
from utils.time_utils import check_now_time
from utils.time_utils import get_time_by_ids
from db.connection import MySQLPool
import bcrypt
class DatabaseManager:
def __init__(self):
# 使用MySQLPool初始化数据库连接池
self.pool = MySQLPool()
def fetch(self, query, params=None):
conn = self.pool.get_connection()
try:
cursor = conn.cursor()
cursor.execute(query, params or ())
result = cursor.fetchall()
return result
finally:
cursor.close()
conn.close()
def execute(self, query, params=None):
conn = self.pool.get_connection()
try:
cursor = conn.cursor()
cursor.execute(query, params or ())
conn.commit()
return cursor.rowcount
finally:
cursor.close()
conn.close()
def executemany(self, query, params_list):
conn = self.pool.get_connection()
try:
cursor = conn.cursor()
cursor.executemany(query, params_list) # 使用executemany代替execute
conn.commit() # 提交事务
return cursor.rowcount # 返回受影响的行数
except Exception as e:
# 可以在这里添加错误处理逻辑,例如打印错误日志、回滚事务等
print("An error occurred: ", e)
conn.rollback()
return None
finally:
cursor.close()
conn.close()
def user_exists(self, phone_number):
sql = "SELECT 1 FROM user WHERE number=%s LIMIT 1"
result = self.fetch(sql, (phone_number,))
return len(result) > 0
def insert_user(self, user):
sql = """
INSERT INTO user (name, number, password, status)
VALUES (%s, %s, %s, %s)
"""
data = (user.name, user.number, user.password, user.status)
# print(data)
return self.execute(sql, data)
def valid_login(self, number, password_attempt):
# SQL查询获取用户的哈希密码身份和状态
sql = "SELECT password, status,name FROM user WHERE number=%s LIMIT 1"
result = self.fetch(sql, (number,))
print(result)
if result:
stored_hash = result[0]['password'] # 假设结果是密码字段
status = result[0]['status'] # 用户状态
name = result[0]['name']
# 使用bcrypt进行密码验证
if bcrypt.checkpw(password_attempt.encode('utf-8'), stored_hash.encode('utf-8')):
# 密码匹配,返回登录成功,身份和状态
return {'valid': True, 'status': status, 'name': name}
# 密码不匹配或用户不存在,返回登录失败
return {'valid': False, 'status': True}
def get_menu(self, role):
sql = "SELECT menu_name,path FROM menu_items WHERE role=%s ORDER BY `order`"
result = self.fetch(sql, (role,))
return result
def get_all_courses(self):
sql = "SELECT course_name, course_code, credits, description FROM course"
result = self.fetch(sql)
return result
def get_current_teacher_courses(self, teacher_number):
# 使用INNER JOIN连接teacher_class_course表和course表
sql = """
SELECT
c.course_id,
c.course_name,
c.course_code,
c.credits,
tcc.class_name,
m.major,
m.major_id
FROM
teacher_class_course tcc
JOIN
course c ON tcc.course_id = c.course_id
JOIN
major m ON tcc.major_id = m.major_id
WHERE
tcc.teacher_number = %s;
"""
# 执行查询并返回结果
class_student_sql = """SELECT course_id, course_name,class_name,major,major_id,major FROM class_student where teacher_number = %s;"""
result1 = self.fetch(sql, (teacher_number,))
result2 = self.fetch(class_student_sql, (teacher_number))
data = []
# 遍历第一个结果集
if result1:
for row in result1:
data.append({
"course_id": row['course_id'],
"course_name": row['course_name'],
"class_name": row['class_name'],
"major_id": row['major_id'],
"major": row['major']
})
# 遍历第二个结果集
if result2:
for row in result2:
data.append({
"course_id": row['course_id'],
"course_name": row['course_name'],
"class_name": row['class_name'],
"major_id": row['major_id'],
"major": row['major']
})
# 返回包含两个结果集信息的data列表
return data
def get_course_type(self):
sql = "SELECT course_name, course_type FROM course"
result = self.fetch(sql)
return result
def get_announcement_info(self):
sql = "SELECT course_name, course_type FROM course"
result = self.fetch(sql)
return result
def insert_teacher(self, teacher):
sql = "INSERT INTO teacher (name, user_id,teacher_number) VALUES (%s, %s, %s);"
data = (teacher.name, teacher.user_id, teacher.teacher_number)
print(data)
return self.execute(sql, data)
def query_user_id(self, phone_number):
sql = "SELECT user_id from user WHERE number = %s;"
data = self.fetch(sql, phone_number)
return data[0]['user_id']
def insert_student(self, student):
sql = """
INSERT INTO student (student_name, student_number, user_id, major_id, class_name)
VALUES (%s, %s, %s, %s, %s)
"""
data = (student.student_name, student.student_number, student.user_id, student.major_id, student.class_name)
return self.execute(sql, data)
def get_course_name(self, student_number, day_of_week, period_id):
print(f"student_number: {student_number}, day_of_week: {day_of_week}, period_id: {period_id}")
# 从student表获取class_name
sql_student = "SELECT class_name FROM student WHERE student_number = %s;"
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;"
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, (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):
sql = """
INSERT INTO attendance_record (student_number, course_id, course_name, date, status)
VALUES (%s, %s, %s, %s, %s)
"""
val = (student_number, course_id, course_name, date, status)
result = self.execute(sql, val)
return result
def get_class_courses(self, student_number):
# 1. 查询学生的主修专业
major_sql = "SELECT major_id FROM student WHERE student_number = %s;"
major_result = self.fetch(major_sql, student_number)
print(major_result)
major_id = major_result[0]['major_id']
# 2. 查询专业的所有课程ID
course_ids_sql = "SELECT course_id FROM major_course WHERE major_id = %s;"
courses_result = self.fetch(course_ids_sql, major_id)
course_ids = [course['course_id'] for course in courses_result]
# 3. 对每个课程ID查询课程详细信息
courses_data = []
for course_id in course_ids:
course_sql = "SELECT course_name, course_code, credits, description FROM course WHERE course_id = %s;"
result = self.fetch(course_sql, course_id)
courses_data.append(result[0])
return courses_data # 返回课程详细信息列表
def student_get_today_courses(self, student_number, day_of_week):
# 从student表获取class_name
sql_student = "SELECT class_name FROM student WHERE student_number = %s;"
class_name = self.fetch(sql_student, (student_number,))[0]['class_name']
# 使用class_name和day_of_week从schedule表获取course_id
sql_schedule = "SELECT course_id,period_id FROM schedule WHERE day_of_week = %s AND class_name = %s;"
schedule_results = self.fetch(sql_schedule, (day_of_week, class_name))
print(schedule_results)
data = []
for i in schedule_results:
course_id = i['course_id']
sql_course = "SELECT course_name FROM course WHERE course_id = %s;"
course_name = self.fetch(sql_course, (course_id))[0]['course_name']
id = i['period_id']
time = get_time_by_ids(id)
data.append({"course_name": course_name, "time": time})
return data
def teacher_sign_in(self, course_id, course_name, class_name, major_id, date, status, teacher_number):
class_student_sql = "SELECT student_number FROM class_student WHERE teacher_number = %s AND major_id = %s AND class_name = %s"
class_student_result = self.fetch(class_student_sql, (teacher_number, major_id, class_name))
student_sql = "SELECT student_number FROM student WHERE class_name = %s AND major_id = %s;"
student_sql_result = self.fetch(student_sql, (class_name, major_id))
values_list = []
# 检查是否有学生编号被返回
if student_sql_result:
for student in student_sql_result:
# 对于每个学生编号,创建一条记录的值元组
student_number = student['student_number']
values_list.append((student_number, course_id, course_name, date, status))
if class_student_result:
for class_student in class_student_result:
# 注意这里变量名已经修改为class_student
student_number = class_student['student_number']
values_list.append((student_number, course_id, course_name, date, status))
# 如果没有任何学生编号,返回相应的消息
if not values_list:
return {"msg": "当前班级专业没有学生"}
# 构建一次性插入多条记录的SQL语句
attendance_sql = "INSERT INTO attendance_record (student_number, course_id, course_name, date, status) VALUES (%s, %s, %s, %s, %s)"
# 使用executemany一次性插入多条记录
result = self.executemany(attendance_sql, values_list)
print(f"Inserted rows: {result}")
return {"msg": "班级签到成功,签到人数:" + str(result)}
def insert_into_class_student(self, class_students):
try:
values_list = [
(
cs['teacher_number'],
cs['class_name'],
cs['student_name'],
cs['student_number'],
cs['course_id'],
cs['course_name'],
cs['major_id'],
cs['major']
)
for cs in class_students
]
sql = """INSERT INTO class_student
(teacher_number, class_name, student_name, student_number, course_id, course_name, major_id, major)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)"""
result = self.executemany(sql, values_list)
return {"inserted_rows": result}
except Exception as e:
# 这里应根据实际情况记录日志或返回错误信息
print(f"An error occurred: {e}")
return {"error": str(e)}

BIN
files/template.xlsx Normal file

Binary file not shown.

16
models/Student.py Normal file
View File

@@ -0,0 +1,16 @@
class Student:
def __init__(self, student_name, student_number, user_id, major_id, class_name):
self.student_name = student_name
self.student_number = student_number
self.user_id = user_id
self.major_id = major_id
self.class_name = class_name
def __str__(self):
return (f"Student Name: {self.student_name}, "
f"User ID: {self.user_id}, "
f"Student Number: {self.student_number}, "
f"Major ID: {self.major_id}, "
f"Class Name: {self.class_name}")

8
models/Teacher.py Normal file
View File

@@ -0,0 +1,8 @@
class Teacher:
def __init__(self, name, user_id, teacher_number):
self.name = name
self.user_id = user_id
self.teacher_number = teacher_number
def __str__(self):
return f"Teacher: {self.name}, {self.user_id},{self.teacher_number}"

15
models/User.py Normal file
View File

@@ -0,0 +1,15 @@
import bcrypt
class User:
def __init__(self, name, number, password, status):
self.name = name # 用户昵称
self.number = number # 手机号
self.password = self.hash_password(password) # 哈希密码
self.status = status # 状态(是否可用)
def hash_password(self, password):
return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
def __str__(self):
return f"User({self.name}, {self.number}, {'Active' if self.status else 'Inactive'})"

0
models/__init__.py Normal file
View File

286
mysql.sql Normal file
View File

@@ -0,0 +1,286 @@
CREATE TABLE menu_items
(
id INT AUTO_INCREMENT PRIMARY KEY,
menu_name VARCHAR(100),
path VARCHAR(255),
role ENUM('student', 'teacher'),
`order` INT
);
INSERT INTO menu_items (menu_name, role, path, `order`)
VALUES ('课程信息', 'student', '/course-info', 1),
('课程签到', 'student', '/attendance', 2),
('公告信息', 'student', '/announcement', 3),
('签到提醒', 'student', '/attendance-reminder', 4),
('课程类别', 'teacher', '/course-category', 1),
('课程信息', 'teacher', '/course-info', 2),
('课程签到', 'teacher', '/attendance-teacher', 3),
('签到提醒', 'teacher', '/attendance-reminder', 4);
CREATE TABLE course
(
course_id INT AUTO_INCREMENT PRIMARY KEY,
course_name VARCHAR(255) NOT NULL,
course_code VARCHAR(50) NOT NULL,
course_type ENUM('选修', '必修') NOT NULL,
credits INT,
description TEXT
);
INSERT INTO course (course_name, course_code, course_type, credits, description)
VALUES ('大学计算机基础', 'CF001', '必修', 3, '介绍计算机基础知识和应用'),
('计算机导论', 'ITC002', '必修', 2, '概述计算机科学的基本概念和原理'),
('C++程序设计', 'CP001', '必修', 4, '学习C++编程语言及其应用'),
('健康教育', 'HE001', '必修', 1, '提升学生的健康意识和生活方式'),
('线性代数及其应用', 'LAAA001', '必修', 4, '学习线性代数的基础理论和实际应用'),
('高等数学2A', 'AM_2A', '必修', 4, '深化数学知识,包括微积分、矩阵理论等'),
('思想道德修养与法律基础', 'MALS001', '必修', 2, '培养学生的道德素质和法律意识'),
('体育A', 'PE_A', '必修', 1, '提高学生的身体素质和运动技能'),
('英语读写译1A', 'ERWT_1A', '必修', 3, '提升学生的英语阅读、写作和翻译能力'),
('英语听力1A', 'EL_1A', '必修', 2, '提高学生的英语听力理解能力'),
('英语口语1A', 'ES_1A', '必修', 2, '提升学生的英语口语表达能力'),
('法制安全教育', 'LASE001', '必修', 1, '增强学生的法制观念和安全意识'),
('大学二外日语A', 'JSFL_A', '选修', 3, '学习基础的日语语法和词汇'),
('军事理论', 'MT01', '必修', 2, '了解军事基本理论和国防知识'),
('程序设计实践', 'PP02', '必修', 3, '通过实践项目提升编程能力'),
('高等数学2B', 'AM_2B', '必修', 4, '进一步深化高等数学知识'),
('中国近现代史纲要', 'OFCMH010', '必修', 2, '概述中国近现代历史发展脉络'),
('体育B', 'PE_B', '必修', 1, '继续提升学生的身体素质和运动技能'),
('英语听力1B', 'EL_1B', '必修', 2, '进一步提高学生的英语听力理解能力'),
('英语读写译1B', 'ERWT_1B', '必修', 3, '深化英语阅读、写作和翻译能力'),
('英语口语1B', 'ES_1B', '必修', 2, '进一步提升学生的英语口语表达能力'),
('大学生心理健康', 'CSMH01', '必修', 2, '关注和提升大学生的心理健康水平'),
('大学物理2A', 'UP_2A', '必修', 4, '学习大学物理的基础理论和实验'),
('数字逻辑', 'DL002', '必修', 3, '学习数字电路和逻辑设计的基础知识'),
('C++面向对象程序设计', 'CPOB02', '必修', 4, '深入学习C++的面向对象特性及其应用'),
('创新人才与大学文化', 'IND010', '选修', 2, '探讨创新人才培养和大学文化建设'),
('数据结构', 'DS001', '必修', 4, '学习各种数据结构及其算法'),
('Java语言程序设计', 'JS012', '必修', 4, '学习Java编程语言及其应用'),
('离散数学导论', 'IDTM01', '必修', 3, '介绍离散数学的基础知识和应用'),
('计算机网络', 'CN002', '必修', 4, '学习计算机网络的基础理论和协议');
CREATE TABLE attendance_record
(
record_id INT AUTO_INCREMENT PRIMARY KEY,
student_number VARCHAR(20) NOT NULL,
course_id INT NOT NULL,
course_name VARCHAR(255) NOT NULL,
date VARCHAR(255) NOT NULL,
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
(
id INT AUTO_INCREMENT PRIMARY KEY,
major_id VARCHAR(20) NOT NULL UNIQUE,
major VARCHAR(255)
);
INSERT INTO major(major_id,major)
values ('000', '计算机科学与技术'),
('001', '电子工程'),
('002', '经济学'),
('003', '生物学');
CREATE TABLE student
(
student_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
student_number VARCHAR(20) NOT NULL UNIQUE,
student_name VARCHAR(255) NOT NULL,
class_name VARCHAR(20) NOT NULL,
major_id VARCHAR(20) NOT NULL,
FOREIGN KEY (user_id) REFERENCES user (user_id),
FOREIGN KEY (major_id) REFERENCES major (major_id)
);
INSERT INTO student (user_id, student_number, student_name, class_name,major_id)
VALUES
(5,'X202301000001','学生1','2023级01班','000'),
(6,'X202301000002','学生2','2023级01班','000'),
(7,'X202301000003','学生3','2023级01班','000'),
(8,'X202301000004','学生4','2023级01班','000'),
(9,'X202302000005','学生5','2023级02班','000'),
(10,'X202302000006','学生6','2023级02班','000'),
(11,'X202302000007','学生7','2023级02班','000'),
(12,'X202302000008','学生8','2023级02班','000'),
(13,'X202303000009','学生9','2023级03班','001'),
(14,'X202303000010','学生10','2023级03班','001'),
(15,'X202303000011','学生11','2023级03班','001'),
(16,'X202303000012','学生12','2023级03班','001');
CREATE TABLE teacher
(
teacher_id INT AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
user_id INT,
teacher_number VARCHAR(20) NOT NULL UNIQUE,
PRIMARY KEY (teacher_id),
FOREIGN KEY (user_id) REFERENCES user (user_id)
);
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
(
id INT AUTO_INCREMENT PRIMARY KEY,
major_id VARCHAR(20) NOT NULL,
course_id INT NOT NULL,
FOREIGN KEY (major_id) REFERENCES major (major_id),
FOREIGN KEY (course_id) REFERENCES course (course_id)
);
INSERT INTO major_course (major_id, course_id) VALUES
('000',1),
('000',2),
('000',3),
('000',4),
('000',5),
('000',6),
('000',7),
('000',8);
CREATE TABLE teacher_class_course
(
id INT AUTO_INCREMENT,
teacher_number VARCHAR(20) NOT NULL,
course_id INT NOT NULL,
class_name VARCHAR(20) NOT NULL,
major_id VARCHAR(20) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (teacher_number) REFERENCES teacher (teacher_number),
FOREIGN KEY (course_id) REFERENCES course (course_id),
FOREIGN KEY (major_id) REFERENCES major (major_id)
);
INSERT INTO teacher_class_course(teacher_number, course_id, class_name,major_id) VALUES
('G0001',1,'2023级01班','000'),
('G0001',3,'2023级01班','000'),
('G0002',2,'2023级02班','000'),
('G0002',4,'2023级02班','000'),
('G0001',1,'2023级02班','000'),
('G0001',3,'2023级02班','000'),
('G0002',2,'2023级03班','001'),
('G0002',4,'2023级03班','001');
CREATE TABLE schedule
(
schedule_id INT AUTO_INCREMENT UNIQUE,
day_of_week INT NOT NULL,
period_id INT NOT NULL,
teacher_number VARCHAR(20) NOT NULL,
class_name VARCHAR(20) NOT NULL,
course_id INT NOT NULL,
PRIMARY KEY (schedule_id),
FOREIGN KEY (period_id) REFERENCES time_period (period_id),
FOREIGN KEY (teacher_number) REFERENCES teacher (teacher_number),
FOREIGN KEY (course_id) REFERENCES course (course_id)
);
INSERT INTO schedule (day_of_week, period_id, teacher_number, class_name, course_id) VALUES
(1,1,'G0001','2023级01班',1),
(1,2,'G0001','2023级01班',3),
(1,3,'G0002','2023级01班',2),
(1,4,'G0002','2023级01班',4),
(2,1,'G0001','2023级01班',1),
(2,2,'G0001','2023级01班',3),
(2,3,'G0002','2023级01班',2),
(2,4,'G0002','2023级01班',4),
(3,1,'G0001','2023级01班',1),
(3,2,'G0001','2023级01班',3),
(3,3,'G0002','2023级01班',2),
(3,4,'G0002','2023级01班',4),
(4,1,'G0001','2023级01班',1),
(4,2,'G0001','2023级01班',3),
(4,3,'G0002','2023级01班',2),
(4,4,'G0002','2023级01班',4),
(5,1,'G0001','2023级01班',1),
(5,2,'G0001','2023级01班',3),
(5,3,'G0002','2023级01班',2),
(5,4,'G0002','2023级01班',4),
(1,1,'G0002','2023级03班',2),
(1,2,'G0002','2023级03班',4),
(1,3,'G0001','2023级02班',1),
(1,4,'G0001','2023级02班',3),
(2,1,'G0002','2023级02班',2),
(2,2,'G0002','2023级02班',4),
(2,3,'G0001','2023级02班',1),
(2,4,'G0001','2023级02班',3),
(3,1,'G0002','2023级03班',2),
(3,2,'G0002','2023级03班',4),
(3,3,'G0001','2023级02班',1),
(3,4,'G0001','2023级02班',3),
(4,1,'G0002','2023级02班',2),
(4,2,'G0002','2023级02班',4),
(4,3,'G0001','2023级02班',1),
(4,4,'G0001','2023级02班',3),
(5,1,'G0002','2023级03班',2),
(5,2,'G0002','2023级03班',4),
(5,3,'G0001','2023级02班',1),
(5,4,'G0001','2023级02班',3);

View File

@@ -1,7 +1,13 @@
bcrypt==4.1.2
blinker==1.7.0 blinker==1.7.0
click==8.1.7 click==8.1.7
colorama==0.4.6
DBUtils==3.0.3
et-xmlfile==1.1.0
Flask==3.0.0 Flask==3.0.0
itsdangerous==2.1.2 itsdangerous==2.1.2
Jinja2==3.1.2 Jinja2==3.1.2
MarkupSafe==2.1.3 MarkupSafe==2.1.3
openpyxl==3.1.2
PyMySQL==1.1.0
Werkzeug==3.0.1 Werkzeug==3.0.1

0
utils/__init__.py Normal file
View File

2
utils/allowed_files.py Normal file
View File

@@ -0,0 +1,2 @@
def allowed_excel(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in {'xlsx', 'xls'}

30
utils/parse_table.py Normal file
View File

@@ -0,0 +1,30 @@
from openpyxl import load_workbook
def parse_table(file_path, number):
# 加载Excel工作簿
work_book = load_workbook(file_path)
# 选择工作簿中的第一个工作表
sheet = work_book.active
# 创建一个列表来存储所有行的数据
data = []
# 从第二行开始遍历(假设第一行是标题行)
for row in sheet.iter_rows(min_row=2, values_only=True):
# 创建一个字典来存储每行的数据
row_data = {
"teacher_number": number,
"class_name": row[0], # 班级
"student_name": row[1], # 姓名
"student_number": row[2], # 学号
"course_id": row[3], # 课程id
"course_name": row[4], # 课程
"major_id": row[5], # 专业代码
"major": row[6]
}
data.append(row_data)
# 返回解析后的数据
return data

35
utils/time_utils.py Normal file
View File

@@ -0,0 +1,35 @@
import datetime
time_periods = {
1: {"period_name": "一、二节", "start_time": "08:00:00", "end_time": "09:30:00"},
2: {"period_name": "三、四节", "start_time": "10:00:00", "end_time": "11:30:00"},
3: {"period_name": "五、六节", "start_time": "14:30:00", "end_time": "16:00:00"},
4: {"period_name": "七、八节", "start_time": "16:30:00", "end_time": "18:00:00"}
}
def check_now_time():
# 获取当前时间
current_time = datetime.datetime.now().time()
# 遍历time_periods的每个时间段
for period_id, period_info in time_periods.items():
start_time = datetime.datetime.strptime(period_info["start_time"], "%H:%M:%S").time()
end_time = datetime.datetime.strptime(period_info["end_time"], "%H:%M:%S").time()
# 检查当前时间是否在时间段内
if start_time <= current_time <= end_time:
return period_id, period_info["period_name"]
# 如果当前时间不在任何一个时间段内
return None, "当前不在任何课程时间段内"
def get_time_by_ids(id):
# 获取对应的时间信息并添加到结果列表
period = time_periods.get(id)
if period: # 确保id是有效的
return period["start_time"][0:5] + "-" + period["end_time"][0:5]
else:
return None