diff --git a/README.md b/README.md index a1c2b14..a91a916 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ - 学生端和教师端都能直接看到全部组及成员的学号、姓名,无需点开详情。 - 教师可切换“当前开放登录班级”,未开放班级不能登录。 - 教师可停止/恢复学生分组操作。 +- 教师可在“学生账号”页签中修改指定学生的登录密码;修改后该学生已有会话会退出。 - 停止后学生只能查看;教师仍可手工建组、改名、设组长、加入、移出、解散。 - 教师可导出当前班分组为 `.xlsx`。 diff --git a/group_system/http/api.py b/group_system/http/api.py index 5ff8a6d..f1e0231 100644 --- a/group_system/http/api.py +++ b/group_system/http/api.py @@ -183,6 +183,17 @@ class GroupRequestHandler: status = HTTPStatus.OK if ok else HTTPStatus.BAD_REQUEST return json_response(status, {"ok": ok, "message": message}) + if action == "teacher/student-password" and method == "POST": + session, error = self.require_teacher() + if error: + return error + data = self.read_json() + ok, message = self.store.teacher_set_student_password( + int(data.get("student_id", 0)), data.get("new_password", "") + ) + status = HTTPStatus.OK if ok else HTTPStatus.BAD_REQUEST + return json_response(status, {"ok": ok, "message": message}) + if action == "teacher/group-limit" and method == "POST": _, error = self.require_teacher() if error: diff --git a/group_system/storage/accounts.py b/group_system/storage/accounts.py index f250c91..8fd9f26 100644 --- a/group_system/storage/accounts.py +++ b/group_system/storage/accounts.py @@ -136,6 +136,30 @@ class AccountStoreMixin: ) return True, "密码修改成功" + def teacher_set_student_password( + self, student_id: int, new_password: str + ) -> tuple[bool, str]: + """教师后台重置指定学生的登录密码。""" + new_password = new_password or "" + if len(new_password) < 8: + return False, "新密码至少 8 位" + with self.lock, self.connection: + student = self.connection.execute( + "SELECT student_no FROM students WHERE id=?", (student_id,) + ).fetchone() + if student is None: + return False, "学生不存在" + if new_password == student["student_no"]: + return False, "新密码不能设置为学号" + self.connection.execute( + "UPDATE students SET password_hash=?, must_change_password=0 WHERE id=?", + (hash_password(new_password), student_id), + ) + self.connection.execute( + "DELETE FROM sessions WHERE student_id=?", (student_id,) + ) + return True, f"已修改 {student['student_no']} 的密码" + def get_student(self, student_id: int) -> dict[str, Any] | None: with self.lock: row = self.connection.execute("SELECT * FROM students WHERE id=?", (student_id,)).fetchone() diff --git a/static/style.css b/static/style.css index a507dfb..f768c01 100644 --- a/static/style.css +++ b/static/style.css @@ -124,6 +124,14 @@ h3 { margin: 0; font-size: 17px; } .import-label:hover { background: #e2e8f0; } .settings-divider { height: 1px; margin: 26px 0 22px; background: var(--border); } .settings-password-line { margin-top: 14px; } +.student-search { max-width: 560px; } +.student-search + .account-password-form { margin-top: 14px; } +.account-password-form { + display: grid; grid-template-columns: max-content minmax(220px, 340px); + gap: 12px 14px; align-items: center; max-width: 560px; +} +.account-password-form .field-label { color: var(--muted); font-size: 14px; font-weight: 600; } +.account-password-form button { grid-column: 2; justify-self: start; } .grid { display: grid; grid-template-columns: repeat(auto-fill,minmax(290px,1fr)); gap: 16px; @@ -185,6 +193,8 @@ h3 { margin: 0; font-size: 17px; } .admin-tab { min-width: 96px; height: 38px; font-size: 13px; } .class-panel, .import-label, .create-group-line { margin-bottom: 14px; } .form-line { flex-direction: column; align-items: stretch; } + .account-password-form { grid-template-columns: 1fr; } + .account-password-form button { grid-column: auto; justify-self: stretch; } .form-line button, .form-line span { width: 100%; text-align: center; } .form-line span { height: auto; color: var(--muted); font-size: 13px; } } diff --git a/static/teacher.html b/static/teacher.html index d7e7f51..69f7417 100644 --- a/static/teacher.html +++ b/static/teacher.html @@ -50,6 +50,7 @@ @@ -71,6 +72,25 @@
+ +