From 88d63335f0842fa64b4687d7207526dff2c8d4e1 Mon Sep 17 00:00:00 2001 From: CakeCN Date: Sun, 24 Aug 2025 21:01:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0teacher=E7=99=BB=E9=99=86?= =?UTF-8?q?=E5=92=8Cteacher=E4=B8=BB=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Html/app.py | 76 ++++++++++++++++- Html/db/data/user/TCake.json | 1 + Html/db/data/user/user_id_list.json | 34 ++++++-- Html/db/user_list.py | 15 +++- Html/static/css/login.css | 52 +++++++++++ Html/static/css/teacherboard.css | 123 +++++++++++++++++++++++++++ Html/static/js/login.js | 91 +++++++++++++------- Html/static/js/register_teacher.js | 41 +++++++++ Html/static/js/teacherboard.js | 69 +++++++++++++++ Html/templates/login.html | 27 ++++-- Html/templates/navbar.html | 50 ++++++++++- Html/templates/register.html | 2 +- Html/templates/register_teacher.html | 36 ++++++++ Html/templates/teacherboard.html | 78 +++++++++++++++++ 14 files changed, 636 insertions(+), 59 deletions(-) create mode 100644 Html/db/data/user/TCake.json create mode 100644 Html/static/css/teacherboard.css create mode 100644 Html/static/js/register_teacher.js create mode 100644 Html/static/js/teacherboard.js create mode 100644 Html/templates/register_teacher.html create mode 100644 Html/templates/teacherboard.html diff --git a/Html/app.py b/Html/app.py index c98ccf3..2d7f9e0 100644 --- a/Html/app.py +++ b/Html/app.py @@ -1,3 +1,4 @@ +from functools import wraps from flask import Flask, redirect, session, request, jsonify, render_template, send_from_directory, url_for from flask_cors import CORS from flask_socketio import SocketIO, join_room, emit, Namespace @@ -303,6 +304,11 @@ def vscode_data(): print(f"Received data from VSCode: {data}") return jsonify({"status": "success", "received": data}) +@app.route('/logout') +def logout(): + session.pop('user_id', None) + return redirect(url_for('login')) + @app.route('/get_session') def get_session(): user_session = session.get('user_id', 'default_session') @@ -317,10 +323,45 @@ Login users_list = UserList() course_list = CourseList() +def require_teacher(func): + @wraps(func) + def wrapper(*args, **kwargs): + if 'user_id' not in session or session['user_id'] not in uuid2username: + return redirect(url_for('login')) + user_id = session['user_id'] + username = uuid2username[user_id] + if users_list.get_user_is_teacher(username) is None or users_list.get_user_is_teacher(username) == False: + return redirect(url_for('login')) + return func(*args, **kwargs) + return wrapper + +def require_user(func): + @wraps(func) + def wrapper(*args, **kwargs): + if 'user_id' not in session or session['user_id'] not in uuid2username: + return redirect(url_for('login')) + return func(*args, **kwargs) + return wrapper + @app.route('/register') def register(): return render_template('register.html') +@app.route('/register_teacher') +def register_teacher(): + return render_template('register_teacher.html') + +@app.route('/register_teacher_post', methods=['POST']) +def register_teacher_post(): + data = request.get_json() + username = data.get('username') + password = data.get('password') + if users_list.has_user(username) is not None: + users_list.add_user(username, password, teacher=True) + create_user_json(username, USER_DATA_DIR) + return jsonify({'success': True, 'message': '注册成功'}) + else: return jsonify({'success': False, 'message': '用户已存在,请更换用户名'}) + @app.route('/register_post', methods=['POST']) def register_post(): data = request.get_json() @@ -362,14 +403,39 @@ def login_post(): uuid2username[session['user_id']] = username return jsonify({'success': True, 'message': '登录成功'}) +@app.route('/login_teacher_post', methods=['POST']) +def login_teacher_post(): + data = request.get_json() + username = data.get('username') + password = data.get('password') + pswd = users_list.get_user_pswd(username) + is_teacher = users_list.get_user_is_teacher(username) + if is_teacher is None or is_teacher == False: + return jsonify({'success': False, 'message': '用户名不存在或非教师账号'}) + if pswd is None: + return jsonify({'success': False, 'message': '用户名或密码错误'}) + if pswd != password: + return jsonify({'success': False, 'message': '用户名或密码错误'}) + session['user_id'] = 'user_' + str(uuid.uuid4()) + print("user "+ username + "uuid is"+ session['user_id']) + username2uuid[username] = session['user_id'] + uuid2username[session['user_id']] = username + return jsonify({'success': True, 'message': '登录成功'}) + +@app.route('/teacherboard') +@require_teacher +def teacherboard(): + return render_template('teacherboard.html') + + + user_id2UserClass = {} ''' DashBoard ''' @app.route('/dashboard') +@require_user def dashboard(): - if 'user_id' not in session or session['user_id'] not in uuid2username: - return redirect(url_for('login')) user_id = session['user_id'] username = uuid2username[user_id] if (user_id not in user_id2UserClass): @@ -385,13 +451,14 @@ Course ''' @app.route('/course/') +@require_user def course(course_id): c = load_course_from_json(course_id, course_data_dir = COURSE_DATA_DIR) return render_template('course.html', course_id=course_id, course_data=c) @app.route('/select_course', methods=['POST']) +@require_user def select_course(): - if ('user_id' not in session or session['user_id'] not in user_id2UserClass):return redirect(url_for('login')) user = user_id2UserClass[session['user_id']] data = request.get_json() course_id = data.get('course_id') @@ -401,6 +468,7 @@ def select_course(): @app.route('/') +@require_user def home_index(): selected_courses=[] if ('user_id' in session): @@ -425,5 +493,5 @@ app.my_function = MyFunction() if __name__ == '__main__': socketio.on_namespace(VSCodeNamespace('/vscode')) socketio.on_namespace(AgentNamespace('/agent')) - socketio.run(app, host='0.0.0.0', port=5550, debug=False,allow_unsafe_werkzeug=True) + socketio.run(app, host='0.0.0.0', port=5551, debug=False,allow_unsafe_werkzeug=True) diff --git a/Html/db/data/user/TCake.json b/Html/db/data/user/TCake.json new file mode 100644 index 0000000..a29d6df --- /dev/null +++ b/Html/db/data/user/TCake.json @@ -0,0 +1 @@ +{"user_id": "TCake", "select_course": [], "head_icon": "", "course_process_dict": {}} \ No newline at end of file diff --git a/Html/db/data/user/user_id_list.json b/Html/db/data/user/user_id_list.json index 5caaf9d..762a4fa 100644 --- a/Html/db/data/user/user_id_list.json +++ b/Html/db/data/user/user_id_list.json @@ -1,8 +1,30 @@ { - "user123": "password123", - "user456": "password456", - "user789": "password789", - "cake": "123123", - "a": "123123", - "CakeCN": "123123" + "user123": { + "password": "password123", + "teacher": false + }, + "user456": { + "password": "password456", + "teacher": false + }, + "user789": { + "password": "password789", + "teacher": false + }, + "cake": { + "password": "123123", + "teacher": false + }, + "a": { + "password": "123123", + "teacher": false + }, + "CakeCN": { + "password": "123123", + "teacher": false + }, + "TCake": { + "password": "123123", + "teacher": true + } } \ No newline at end of file diff --git a/Html/db/user_list.py b/Html/db/user_list.py index 0e86806..c457159 100644 --- a/Html/db/user_list.py +++ b/Html/db/user_list.py @@ -7,11 +7,11 @@ class UserList: with open(self.db_file, 'r', encoding='utf-8') as f: self.db = json.load(f) - def add_user(self, user_id, password): + def add_user(self, user_id, password, teacher=False): """添加新的用户登录信息""" # 检查是否已经有该用户 if user_id not in self.db: - self.db[user_id] = password + self.db[user_id] = {'password': password, 'teacher': teacher} with open(self.db_file, 'w', encoding='utf-8') as f: json.dump(self.db, f, indent=4) else: @@ -21,17 +21,24 @@ class UserList: """检查用户是否存在""" return user_id in self.db + def get_user_is_teacher(self, user_id): + """获取指定 user_id 的用户是否为教师""" + if user_id in self.db: + result = self.db[user_id] + return result['teacher'] + return None + def get_user_pswd(self, user_id): """获取指定 user_id 的用户信息""" if user_id in self.db: result = self.db[user_id] - return result + return result['password'] return None def update_password(self, user_id, new_password): """更新用户的密码""" assert self.db.get(user_id) is not None, f"User with ID {user_id} does not exist." - self.db[user_id] = new_password + self.db[user_id] = {'password': new_password, 'teacher': self.db[user_id]['teacher']} with open(self.db_file, 'w', encoding='utf-8') as f: json.dump(self.db, f, indent=4) diff --git a/Html/static/css/login.css b/Html/static/css/login.css index f87219b..987aebd 100644 --- a/Html/static/css/login.css +++ b/Html/static/css/login.css @@ -41,6 +41,27 @@ body { margin-bottom: 20px; } +.login-switch { + display: flex; + justify-content: space-around; + margin-bottom: 20px; +} + +.switch-btn { + background-color: #2575fc; + color: white; + padding: 10px 20px; + font-size: 16px; + border: none; + border-radius: 8px; + cursor: pointer; + transition: background-color 0.3s; +} + +.switch-btn:hover { + background-color: #6a11cb; +} + .login-form { display: flex; flex-direction: column; @@ -72,7 +93,34 @@ body { border-color: #2575fc; outline: none; } +/* 基本按钮样式 */ +.role-btn { + background-color: #2575fc; /* 默认的蓝色背景(学生登录) */ + color: white; + border: none; + border-radius: 8px; + padding: 10px 20px; + font-size: 16px; + cursor: pointer; + transition: background-color 0.3s, transform 0.3s; /* 背景色平滑过渡,按钮缩放 */ + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} +/* 按钮悬停效果 */ +.role-btn:hover { + background-color: #6a11cb; /* 悬停时变为深蓝色 */ + transform: scale(1.05); /* 按钮放大效果 */ +} + +/* 切换到教师登录时的背景色 */ +.teacher-login .role-btn { + background-color: #005848; /* 深蓝色背景(教师登录) */ +} + +/* 切换到教师登录时的悬停效果 */ +.teacher-login .role-btn:hover { + background-color: #017c6e; +} .login-btn { background-color: #2575fc; color: white; @@ -82,6 +130,10 @@ body { border-radius: 8px; cursor: pointer; transition: background-color 0.3s; + width: 100%; + margin-top: 30px; + margin-bottom: 30px; + } .login-btn:hover { diff --git a/Html/static/css/teacherboard.css b/Html/static/css/teacherboard.css new file mode 100644 index 0000000..4dbe05e --- /dev/null +++ b/Html/static/css/teacherboard.css @@ -0,0 +1,123 @@ +/* Teacherboard 样式 */ +.teacherboard { + padding: 20px; + font-family: Arial, sans-serif; +} + +.teacherboard-title { + text-align: center; + font-size: 32px; + margin-bottom: 20px; +} + +.course-cards { + display: flex; + flex-wrap: wrap; + justify-content: space-around; + gap: 20px; +} + +.course-card { + width: 250px; + background-color: #f9f9f9; + padding: 20px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + cursor: pointer; +} + +.course-image { + width: 100%; + height: 150px; + object-fit: cover; + border-radius: 5px; +} + +.course-info { + margin-top: 10px; +} + +.course-name { + font-size: 20px; + font-weight: bold; +} + +.course-description { + font-size: 14px; + color: #555; +} + +/* 课程目录弹出框 */ +.course-details { + position: fixed; + top: 0; + right: 0; + width: 300px; + height: 100%; + background-color: #fff; + box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1); + padding: 20px; + display: none; +} + +.details-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.details-header button { + background-color: transparent; + border: none; + font-size: 24px; + cursor: pointer; +} + +.details-content { + padding-right: 20px; +} + +.add-chapter-btn { + background-color: #2575fc; + color: white; + padding: 10px; + border-radius: 5px; + cursor: pointer; +} + +/* 编辑弹窗 */ +.edit-modal { + display: none; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 400px; + background-color: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); +} + +.edit-modal-content { + display: flex; + flex-direction: column; +} + +.edit-modal-content input, +.edit-modal-content textarea { + margin-bottom: 10px; + padding: 10px; + font-size: 16px; + border: 1px solid #ddd; + border-radius: 5px; +} + +.edit-modal-content button { + background-color: #2575fc; + color: white; + padding: 10px; + border-radius: 5px; + cursor: pointer; +} diff --git a/Html/static/js/login.js b/Html/static/js/login.js index a040a9b..9fbfca8 100644 --- a/Html/static/js/login.js +++ b/Html/static/js/login.js @@ -1,47 +1,78 @@ document.addEventListener('DOMContentLoaded', function() { - // 获取登录表单及按钮 - const loginForm = document.querySelector('.login-form'); - const loginButton = document.querySelector('.login-btn'); + const switchRoleButton = document.getElementById('switch-role'); + const loginForm = document.getElementById('login-form'); + const usernameLabel = document.getElementById('username-label'); const usernameInput = document.getElementById('username'); const passwordInput = document.getElementById('password'); - - // 在点击登录按钮时提交表单 - loginButton.addEventListener('click', function(event) { - event.preventDefault(); // 防止表单的默认提交行为 - - // 获取输入框的值 + const registerLinkHref = document.getElementById('register-link-href'); + + let isTeacherLogin = false; + + // 切换角色按钮点击事件 + switchRoleButton.addEventListener('click', function() { + isTeacherLogin = !isTeacherLogin; // 切换状态 + toggleButtonColor(isTeacherLogin); + toggleLoginFields(isTeacherLogin); + }); + // 根据角色切换按钮背景色 + function toggleButtonColor(isTeacher) { + if (isTeacher) { + loginForm.classList.add('teacher-login'); + } else { + loginForm.classList.remove('teacher-login'); + } + } + // 切换显示不同的表单字段 + function toggleLoginFields(isTeacher) { + if (isTeacher) { + // 显示教师登录字段 + usernameLabel.textContent = '教师用户名'; + switchRoleButton.textContent = '切换到学生登录'; // 更新按钮文本 + registerLinkHref.href = '/register_teacher'; + registerLinkHref.textContent = '注册新教师账号'; + } else { + // 显示学生登录字段 + usernameLabel.textContent = '学生用户名'; + switchRoleButton.textContent = '切换到教师登录'; // 更新按钮文本 + registerLinkHref.href = '/register'; + registerLinkHref.textContent = '注册新学生账号'; + } + } + + // 处理登录提交 + loginForm.addEventListener('submit', function(event) { + event.preventDefault(); // 防止默认提交 + const username = usernameInput.value.trim(); const password = passwordInput.value.trim(); - - // 检查输入框是否为空 + if (!username || !password) { - alert('用户名和密码不能为空'); + alert('请填写所有必需的字段'); return; } - // 显示加载提示 - loginButton.innerHTML = '登录中...'; - loginButton.disabled = true; - - // 创建一个请求对象 - const data = { - username: username, - password: password - }; - - // 使用 Fetch API 发送 POST 请求到 Flask 后端 - fetch('/login_post', { + // 登录请求的 URL 依据角色不同而不同 + const loginUrl = isTeacherLogin ? '/login_teacher_post' : '/login_post'; + + const data = isTeacherLogin ? { username, password } : { username, password }; + + // 发送登录请求 + fetch(loginUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data) // 将数据转换为 JSON 格式发送 + body: JSON.stringify(data) // 将数据发送到后端 }) - .then(response => response.json()) // 解析响应数据 + .then(response => response.json()) .then(data => { if (data.success) { // 登录成功,跳转到主页 - window.location.href = '/dashboard'; // 根据需要修改跳转的路径 + if (isTeacherLogin) { + window.location.href = '/teacherboard'; + } else { + window.location.href = '/dashboard'; + } } else { // 登录失败,提示错误信息 alert('登录失败: ' + data.message); @@ -50,11 +81,7 @@ document.addEventListener('DOMContentLoaded', function() { .catch(error => { console.error('Error:', error); alert('登录请求失败,请稍后重试'); - }) - .finally(() => { - // 恢复按钮状态 - loginButton.innerHTML = '登录'; - loginButton.disabled = false; }); }); + }); diff --git a/Html/static/js/register_teacher.js b/Html/static/js/register_teacher.js new file mode 100644 index 0000000..c73ee6e --- /dev/null +++ b/Html/static/js/register_teacher.js @@ -0,0 +1,41 @@ +document.addEventListener('DOMContentLoaded', function() { + const registerTeacherForm = document.getElementById('register-teacher-form'); + + registerTeacherForm.addEventListener('submit', function(event) { + event.preventDefault(); // 阻止表单默认提交行为 + + const username = document.getElementById('teacher-username').value; + const email = document.getElementById('teacher-email').value; + const password = document.getElementById('teacher-password').value; + const confirmPassword = document.getElementById('teacher-confirm-password').value; + + // 检查密码和确认密码是否一致 + if (password !== confirmPassword) { + alert('密码和确认密码不一致'); + return; + } + + // 使用 Fetch API 发送注册请求 + fetch('/register_teacher_post', { // 教师注册路由 + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': '{{ csrf_token() }}' // 如果使用 CSRF 保护 + }, + body: JSON.stringify({ username, email, password }) // 发送数据 + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + alert('教师注册成功'); + window.location.href = '/login'; // 注册成功后跳转到登录页面 + } else { + alert('注册失败: ' + data.message); + } + }) + .catch(error => { + console.error('Error:', error); + alert('注册失败'); + }); + }); +}); diff --git a/Html/static/js/teacherboard.js b/Html/static/js/teacherboard.js new file mode 100644 index 0000000..bbd86e4 --- /dev/null +++ b/Html/static/js/teacherboard.js @@ -0,0 +1,69 @@ +function show_teacher_data(user_data, user_course_data) { + // 这里可以根据user_course_data生成课程卡片 + const courseCardsContainer = document.getElementById('course-cards'); + user_course_data.forEach(course => { + const courseCard = document.createElement('div'); + courseCard.classList.add('course-card'); + courseCard.onclick = function() { openCourseDetails(course.id); }; + courseCard.innerHTML = ` + 课程封面 +
+

${course.name}

+

${course.description}

+
+ `; + courseCardsContainer.appendChild(courseCard); + }); +} + +function openCourseDetails(courseId) { + // 打开课程目录 + const courseDetails = document.getElementById('courseDetails'); + const courseTitle = document.getElementById('course-title'); + courseTitle.textContent = "课程名称: " + courseId; // 假设这里显示课程ID,实际情况应该是课程名称 + courseDetails.style.display = 'block'; + + // 获取章节数据并渲染 + renderChapters(courseId); +} + +function closeCourseDetails() { + document.getElementById('courseDetails').style.display = 'none'; +} + +function renderChapters(courseId) { + const chapterList = document.getElementById('chapter-list'); + chapterList.innerHTML = ""; // 清空章节列表 + // 假设从服务器获取课程章节数据 + const chapters = [ + { title: "第一章:算法基础", lessons: ["算法简介", "算法设计方法"] }, + { title: "第二章:数据结构", lessons: ["数组", "链表"] } + ]; + + chapters.forEach(chapter => { + const chapterItem = document.createElement('li'); + chapterItem.innerHTML = ` + ${chapter.title} + + + `; + chapterList.appendChild(chapterItem); + }); +} + +function addChapter() { + // 新增章节的逻辑 + alert("新增章节功能"); +} + +function addLesson(chapterTitle) { + // 新增课时的逻辑 + alert("新增课时功能: " + chapterTitle); +} + +function editLesson(lesson) { + // 编辑课时的逻辑 + alert("编辑课时: " + lesson); +} diff --git a/Html/templates/login.html b/Html/templates/login.html index 7126d29..9634117 100644 --- a/Html/templates/login.html +++ b/Html/templates/login.html @@ -3,7 +3,7 @@ - 用户登录 + 登录 @@ -11,27 +11,36 @@ diff --git a/Html/templates/navbar.html b/Html/templates/navbar.html index b204050..227ace5 100644 --- a/Html/templates/navbar.html +++ b/Html/templates/navbar.html @@ -36,10 +36,50 @@ height: 40px; border-radius: 50%; border: 2px solid white; + cursor: pointer; } - .avatar{ + + .avatar { margin-left: 200px; } + + /* 控制浮框 */ + .dropdown { + position: relative; + display: inline-block; + } + + .dropdown-content { + display: none; + position: absolute; + right: 0; + background-color: white; + color: black; + min-width: 200px; + box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.2); + border-radius: 10px; + z-index: 1; + padding: 10px; + font-size: 14px; + } + + .dropdown-content a { + color: black; + text-decoration: none; + padding: 10px; + display: block; + transition: background-color 0.3s; + } + + .dropdown-content a:hover { + background-color: #f1c40f; + } + + /* 显示浮框 */ + .dropdown:hover .dropdown-content { + display: block; + } +