From 6883a999e9ba5ccd8060bfb3a39a311a9c9d453f Mon Sep 17 00:00:00 2001 From: CakeCN Date: Sat, 6 Sep 2025 21:27:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AD=A6=E7=94=9F=E7=AB=AF=E9=80=89=E8=AF=BE?= =?UTF-8?q?=E8=BF=9B=E8=AF=BE=E9=97=AD=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Html/apps/config.py | 2 +- Html/apps/extensions.py | 2 +- Html/apps/services/cos_service.py | 19 ++ Html/apps/services/course_service.py | 41 +++- Html/apps/services/markdown_service.py | 35 +++- Html/apps/services/memory_service.py | 7 +- Html/apps/services/user_db_service.py | 185 +++++++++++++++++ Html/apps/services/user_service.py | 7 +- Html/apps/sockets/namespaces.py | 24 +-- Html/apps/static/css/desktop.css | 86 +++++--- Html/apps/static/js/dashboard.js | 189 ++++++++++-------- Html/apps/static/二分.html | 16 ++ Html/apps/static/基础算法-二分.html | 56 ++++++ Html/apps/templates/dashboard.html | 22 +- Html/apps/templates/desktop.html | 48 +++-- Html/apps/templates/index.html | 16 +- Html/apps/templates/navbar.html | 2 +- Html/apps/views/auth.py | 7 + Html/apps/views/dashboard.py | 36 +++- Html/apps/views/markdown.py | 25 +-- Html/apps/views/vscode.py | 19 +- Html/db/course.py | 5 + .../68bacdfadf5aeae0912f7f18/二分/.config | 1 + .../68bacdfadf5aeae0912f7f18/基础算法/.config | 1 + 24 files changed, 634 insertions(+), 217 deletions(-) create mode 100644 Html/apps/services/user_db_service.py create mode 100644 Html/apps/static/二分.html create mode 100644 Html/apps/static/基础算法-二分.html create mode 100644 study/TCake/68bacdfadf5aeae0912f7f18/二分/.config create mode 100644 study/TCake/68bacdfadf5aeae0912f7f18/基础算法/.config diff --git a/Html/apps/config.py b/Html/apps/config.py index f34d5b8..cc61509 100644 --- a/Html/apps/config.py +++ b/Html/apps/config.py @@ -20,7 +20,7 @@ class Config: SOCKETIO_PING_TIMEOUT = 60 SOCKETIO_PING_INTERVAL = 5 MARKDOWN_DIR = os.path.join(BASE_DIR, "books", "markdown") - STATIC_DIR = os.path.join(BASE_DIR, "static") + STATIC_DIR = os.path.join(BASE_DIR, "apps", "static") IMAGE_DIR = "image" # 你的学生工作区根目录(如无就放到项目 data 目录) diff --git a/Html/apps/extensions.py b/Html/apps/extensions.py index 8ae87fc..bb0bc58 100644 --- a/Html/apps/extensions.py +++ b/Html/apps/extensions.py @@ -2,8 +2,8 @@ import os, sys from flask_sqlalchemy import SQLAlchemy from flask_socketio import SocketIO from flask_cors import CORS -from db.user_list import UserList from db.course_list import CourseList +from .services.user_db_service import UserList from .services.my_function import MyFunction from flask_pymongo import PyMongo from qcloud_cos import CosConfig diff --git a/Html/apps/services/cos_service.py b/Html/apps/services/cos_service.py index 9508e45..c5c3d36 100644 --- a/Html/apps/services/cos_service.py +++ b/Html/apps/services/cos_service.py @@ -1,4 +1,5 @@ from flask import current_app +import requests def upload_file(rb_file, file_name): if file_name is not None and "/" in file_name: @@ -21,3 +22,21 @@ def delete_file(file_name): Key=file_name, ) return True + +def get_text_file(file_name): + response = requests.get(file_name) + + if response.status_code == 200: + print(file_name) + # print(f"get_text_file: {response.text}") + + return response.text + else: + return None + + return response.text + # if file_name is not None and "/" in file_name: + # file_name = file_name.split("/")[-1] + # cos_client = current_app.extensions["cos_client"] + # print(f"get_text_file: {file_name}") + # return cos_client.get_object(Bucket=current_app.config["TENCENT_COS"]["bucket"], Key=file_name)["Body"].read() \ No newline at end of file diff --git a/Html/apps/services/course_service.py b/Html/apps/services/course_service.py index 5ed2445..180ead3 100644 --- a/Html/apps/services/course_service.py +++ b/Html/apps/services/course_service.py @@ -10,21 +10,43 @@ from ..services.cos_service import upload_file, delete_file from ..models.material import Material -# 假设你已有这两个 loader -from db.course import load_course_from_json -def load_course(course_id: str): - return load_course_from_json( - course_id, course_data_dir=current_app.config["COURSE_DATA_DIR"] - ) +def get_new_edit_materials(num: int)->List[Material]: + mongo = current_app.extensions["mongo"] + materials = mongo.db.materials.find().sort("updated_at", -1).limit(num) + returnCode = [] + for material in materials: + returnCode.append(Material(**material)) + returnCode[-1]._id = str(material['_id']) + return returnCode + +def user_selected_course(user_obj): + briefs = [] + for cid in getattr(user_obj, "select_course", []): + course_obj = load_material(cid) + brief={} + brief["_id"] = cid + brief["name"] = course_obj.name + brief["description"] = course_obj.description + brief["image_url"] = course_obj.image_url + brief["created_at"] = course_obj.created_at.strftime("%Y-%m-%d %H:%M:%S") + brief["updated_at"] = course_obj.updated_at.strftime("%Y-%m-%d %H:%M:%S") + brief["chapters"] = [chapter.model_dump() for chapter in course_obj.chapters] + briefs.append(brief) + return briefs def user_selected_course_briefs(user_obj): """根据用户对象的选课列表,拼装课程简要信息数组""" - course_list = current_app.extensions["course_list"] briefs = [] for cid in getattr(user_obj, "select_course", []): - course_obj = load_course(cid) - brief = course_list.get_course_brief_info(cid, course_obj) + course_obj = load_material(cid) + brief={} + brief["_id"] = cid + brief["name"] = course_obj.name + brief["description"] = course_obj.description + brief["image_url"] = course_obj.image_url + brief["created_at"] = course_obj.created_at.strftime("%Y-%m-%d %H:%M:%S") + brief["updated_at"] = course_obj.updated_at.strftime("%Y-%m-%d %H:%M:%S") briefs.append(brief) return briefs @@ -45,6 +67,7 @@ def create_material(teacher_id, material_name, description, chapters, image_url) def load_material(material_id): mongo = current_app.extensions["mongo"] material = mongo.db.materials.find_one({"_id": ObjectId(material_id)}) + material['_id'] = material_id return Material(**material) def update_material(material_id, chapters): diff --git a/Html/apps/services/markdown_service.py b/Html/apps/services/markdown_service.py index 11bb982..61242c1 100644 --- a/Html/apps/services/markdown_service.py +++ b/Html/apps/services/markdown_service.py @@ -1,10 +1,35 @@ import os, shutil, markdown +from flask import current_app +from ..services.course_service import load_material +from ..services.cos_service import get_text_file -def convert_markdown_to_html(md_file_path: str) -> str: - """读取 markdown 并转成 HTML 片段""" - with open(md_file_path, 'r', encoding='utf-8') as f: - md_content = f.read() - return markdown.markdown(md_content) +def load_full_markdown_file(course_id, chapter_name, lesson_name): + material = load_material(course_id) + for chapter in material.chapters: + if chapter.chapter_name == chapter_name: + for lesson in chapter.lessons: + if lesson.lesson_name == lesson_name: + return get_text_file(lesson.markdown_lesson_file_link), get_text_file(lesson.markdown_prompt_file_name), get_text_file(lesson.markdown_score_prompt_file_link) + return None, None, None + +def load_markdown_file(course_id, chapter_name, lesson_name): + material = load_material(course_id) + file_link = None + for chapter in material.chapters: + if chapter.chapter_name == chapter_name: + for lesson in chapter.lessons: + if lesson.lesson_name == lesson_name: + file_link = lesson.markdown_lesson_file_link + break + break + if file_link is None: + return None + print(f"load_markdown_file: {file_link}") + return get_text_file(file_link) + +def convert_markdown_to_html(md_file: str) -> str: + """将 markdown 文件转成 HTML 片段""" + return markdown.markdown(md_file) def wrap_with_styles(html_content: str) -> str: """加上 CSS 样式和外层 HTML""" diff --git a/Html/apps/services/memory_service.py b/Html/apps/services/memory_service.py index 37d59c8..520a293 100644 --- a/Html/apps/services/memory_service.py +++ b/Html/apps/services/memory_service.py @@ -1,7 +1,6 @@ # myapp/services/memory_service.py import os, json from flask import current_app -from db.user import load_user_from_json # 按你项目实际替换 def save_chapter_memory_by_uuid( user_uuid: str, @@ -14,11 +13,13 @@ def save_chapter_memory_by_uuid( ): uuid2username = current_app.extensions["uuid2username"] user_map = current_app.extensions["user_uuid2UserClass"] # 可复用缓存 - user_data_dir = current_app.config["USER_DATA_DIR"] + users_list = current_app.extensions["users_list"] username = uuid2username[user_uuid] # 若内存里已有用户对象就直接用;否则从 JSON 载入 - user_obj = user_map.get(user_uuid) or load_user_from_json(username, user_data_dir=user_data_dir) + user_obj = user_map.get(user_uuid) or users_list.get_user_by_name( + username + ) user_map[user_uuid] = user_obj user_obj.save_chapter_memory( diff --git a/Html/apps/services/user_db_service.py b/Html/apps/services/user_db_service.py new file mode 100644 index 0000000..10e0e73 --- /dev/null +++ b/Html/apps/services/user_db_service.py @@ -0,0 +1,185 @@ +from flask import current_app +from bson.binary import Binary +import uuid +import os +# Example usage: +import json +from datetime import datetime +import copy +from ..models.material import Material + +class User: + def __init__(self, user_uuid, username, teacher, password, select_course, head_icon, _id, **kwargs): + self.user_uuid = user_uuid # User UUID + self.username = username + self.teacher = teacher + self.password = password + self.select_course = select_course # List of selected courses + self.head_icon = head_icon # URL of the user's head icon + self._id = _id + self.kwargs = kwargs + def save_chapter_memory(self, course_id, lesson_name, subchapter_title, mem_list, score, is_rebuttal): + ''' + Save the memory of a chapter + into the mongo db + ''' + try: + mongo = current_app.extensions["mongo"] + cp = mongo.db.course_process.find_one({"user_uuid": self.user_uuid, "material_id": course_id}) + if not cp: + return + cp['course_last_study_date'] = datetime.now().strftime('%Y-%m-%d') + have_lesson = False + for _ in range(len(cp['lesson_processs'])): + lesson_process, cname, lname = cp['lesson_processs'][_] + if lname == lesson_name: + have_lesson = True + saveed = False + for i in range(len(lesson_process)): + step = lesson_process[i] + if step['title'] == subchapter_title: + step['dialog'] = mem_list + if is_rebuttal: + step['rebuttal_score'] = score + else: + step['score'] = score + step['is_rebuttal'] = is_rebuttal + saveed = True + break + if not saveed: + cp['lesson_processs'].append({ + 'title': subchapter_title, + 'dialog': mem_list, + 'score': score, + 'is_rebuttal': is_rebuttal, + 'rebuttal_score': score if is_rebuttal else 0, + }) + break + if not have_lesson: + cp['lesson_processs'].append(([], chapter_name, lesson_name)) + mongo.db.course_process.update_one({"user_uuid": self.user_uuid, "material_id": course_id}, {"$set": cp}) + except Exception as e: + print("=-=-=-=-=-=") + print(e) + def get_all_course_progress(self): + mongo = current_app.extensions["mongo"] + course_processes = mongo.db.course_process.find({"user_uuid": self.user_uuid}) + for cp in course_processes: + print(f"Course: {cp['material_id']}") + for lessons,chapter_name,lesson_name in cp["lesson_processs"]: + print(f" Lesson ID: {lesson_name}") + for step in lessons: + print(f" Chapter Dialog: {step['dialog']}") + print(f" Chapter Score: {step['score']}") + print(f" Rebuttal: {'Yes' if step['is_rebuttal'] else 'No'}") + if step['is_rebuttal']: + print(f" Rebuttal Score: {step['rebuttal_score']}") + + def get_course_progress(self, course_id): + mongo = current_app.extensions["mongo"] + cp = mongo.db.course_process.find_one({"user_uuid": self.user_uuid, "material_id": course_id}) + return cp + + def select_new_course(self, course_id, course:Material): + try: + if course_id in self.select_course: + return True + self.select_course.append(course_id) + mongo = current_app.extensions["mongo"] + mongo.db.course_process.insert_one({ + 'user_uuid': self.user_uuid, + 'material_id': course_id, + 'lesson_processs':[], + 'course_put_in_date':datetime.now().strftime('%Y-%m-%d'), + 'course_last_study_date':datetime.now().strftime('%Y-%m-%d'), + 'course_total_study_time':0}) + for chapter in course.chapters: + for lesson in chapter.lessons: + mongo.db.course_process.update_one({"user_uuid": self.user_uuid, "material_id": course_id}, {"$push": {"lesson_processs": ([], chapter.chapter_name, lesson.lesson_name)}}) + self.select_course.append(course_id) + mongo.db.users.update_one({"user_uuid": self.user_uuid}, {"$push": {"select_course": course_id}}) + except Exception as e: + print("=-=-=-=-=-=") + print(e) + return False + return True + def save_to_db(self): + current_app.extensions["mongo"].db.users.update_one({"user_uuid": self.user_uuid}, {"$set": self.__json__()}) + + + def __json__(self): + return { + 'username': self.username, + 'teacher': self.teacher, + 'select_course': self.select_course, + 'head_icon': self.head_icon, + } + + + + def to_json(self): + return json.dumps(self.__json__(), indent=4) + + + +class UserList: + + def __init__(self): + """从mongo中获取用户信息""" + self.Users = {} + + def add_user(self, username, password, teacher=False): + """添加新的用户登录信息""" + # 检查是否已经有该用户 + mongo = current_app.extensions["mongo"] + if mongo.db.users.find_one({"username": username}) is None: + mongo.db.users.insert_one({"user_uuid": Binary.from_uuid(uuid.uuid4()), "username": username, "teacher": teacher, "password": password, "select_course": [], "head_icon": ""}) + else: + print(f"User with ID {username} already exists.") + + def get_user_by_name(self, username): + """根据用户ID获取用户信息""" + mongo = current_app.extensions["mongo"] + if username not in self.Users: + self.Users[username] = User(**mongo.db.users.find_one({"username": username})) + return self.Users[username] + else: + return self.Users[username] + + def has_user(self, username): + """检查用户是否存在""" + mongo = current_app.extensions["mongo"] + return mongo.db.users.find_one({"username": username}) is not None + + def get_user_is_teacher(self, username): + """获取指定 user_id 的用户是否为教师""" + mongo = current_app.extensions["mongo"] + if mongo.db.users.find_one({"username": username}) is not None: + result = mongo.db.users.find_one({"username": username}) + return result['teacher'] + return None + + def get_user_pswd(self, username): + """获取指定 user_id 的用户信息""" + mongo = current_app.extensions["mongo"] + if mongo.db.users.find_one({"username": username}) is not None: + result = mongo.db.users.find_one({"username": username}) + return result['password'] + return None + + def update_password(self, username, new_password): + """更新用户的密码""" + assert mongo.db.users.find_one({"username": username}) is not None, f"User with ID {username} does not exist." + mongo.db.users.update_one({"username": username}, {"$set": {"password": new_password, "teacher": mongo.db.users.find_one({"username": username})['teacher']}}) + + def delete_user(self, username): + """删除用户""" + assert mongo.db.users.find_one({"username": username}) is not None, f"User with ID {username} does not exist." + mongo.db.users.delete_one({"username": username}) + + def get_all_users(self): + """获取所有用户的信息""" + mongo = current_app.extensions["mongo"] + return list(mongo.db.users.find()) + + diff --git a/Html/apps/services/user_service.py b/Html/apps/services/user_service.py index b67395e..f49bb85 100644 --- a/Html/apps/services/user_service.py +++ b/Html/apps/services/user_service.py @@ -3,8 +3,6 @@ import os from typing import Tuple from flask import current_app, session -# 假设你已有这两个 loader -from db.user import load_user_from_json def _username_from_session() -> str: """通过 session['user_id'] -> username""" @@ -28,8 +26,9 @@ def get_or_load_current_user(): user_map = current_app.extensions["user_uuid2UserClass"] if user_uuid not in user_map: - user_map[user_uuid] = load_user_from_json( - username, user_data_dir=current_app.config["USER_DATA_DIR"] + users_list = current_app.extensions["users_list"] + user_map[user_uuid] = users_list.get_user_by_name( + username ) return user_map[user_uuid] diff --git a/Html/apps/sockets/namespaces.py b/Html/apps/sockets/namespaces.py index 8d7de7f..6480fbf 100644 --- a/Html/apps/sockets/namespaces.py +++ b/Html/apps/sockets/namespaces.py @@ -4,6 +4,8 @@ from flask import current_app, request, session from flask_socketio import Namespace, join_room, leave_room, emit from ..services.memory_service import save_chapter_memory_by_uuid from ..services.backboard_service import realtime_response +from ..services.markdown_service import load_full_markdown_file + class VSCodeNamespace(Namespace): def on_login(self,data): ex = current_app.extensions @@ -41,22 +43,20 @@ class AgentNamespace(Namespace): data = json.loads(data) user_uuid = data['user_uuid'] course_id = data['course_id'] - chapter_id = data['chapter_id'] + lesson_name = data['lesson_name'] + chapter_name = data['chapter_name'] user_id = uuid2username[user_uuid] session['user_uuid'] = user_uuid print(f'User connected with session user_uuid: {user_uuid}') + + fmd,fmdp,fsp = load_full_markdown_file(course_id, chapter_name, lesson_name) # 从markdown、markdown_prompts、score_prompts中各读取数据folder.md并进行new_agent - with open(f'books/markdown/{course_id}/{chapter_id}.md','r',encoding='UTF-8') as fmd,\ - open(f'books/markdown_prompts/{course_id}/{chapter_id}.md','r',encoding='UTF-8')as fmdp,\ - open(f'books/score_prompts/{course_id}/{chapter_id}.md','r',encoding='UTF-8') as fsp: - markdown = fmd.read() - markdown_prompts = fmdp.read() - score_prompts = fsp.read() - user_uuid, agent = agent_manager.new_agent(course_id, chapter_id, markdown, markdown_prompts, score_prompts, id=user_uuid, - root_path=f'../../study/{user_id}/{course_id}/{chapter_id}') - print(user_uuid) - join_room(user_uuid, namespace='/agent') # 将该用户加入以user_id为名的room - backboard_manager.add_backboard(user_uuid, user_id, course_id, chapter_id, root_path=f'../../study/{user_id}/{course_id}/{chapter_id}') + + user_uuid, agent = agent_manager.new_agent(course_id, lesson_name, fmd, fmdp, fsp, id=user_uuid, + root_path=f'../../study/{user_id}/{course_id}/{chapter_name}/{lesson_name}') + print(user_uuid) + join_room(user_uuid, namespace='/agent') # 将该用户加入以user_id为名的room + backboard_manager.add_backboard(user_uuid, user_id, course_id, lesson_name, root_path=f'../../study/{user_id}/{course_id}/{chapter_name}/{lesson_name}') def on_language(self, language): ex = current_app.extensions diff --git a/Html/apps/static/css/desktop.css b/Html/apps/static/css/desktop.css index eb5d5fc..93b0ac1 100644 --- a/Html/apps/static/css/desktop.css +++ b/Html/apps/static/css/desktop.css @@ -1,9 +1,11 @@ + /* 左右两侧的页面内容 */ .sidebar { background-color: #f0f0f0; - padding: 20px; height: 100%; + display: flex; + flex-direction: column; /* 使子元素垂直排列 */ } body { @@ -16,7 +18,8 @@ .chatbox { flex-grow: 1; padding: 20px; - height: 75%; + width: 100%; + flex: 1; overflow-y: auto; border: 1px solid #ccc; background-color: #f9f9f9; @@ -69,14 +72,7 @@ height: 100%; position: relative; } - /* 中间的拖拽条 */ - .resizer { - width: 5px; - background-color: #ccc; - cursor: ew-resize; - position: relative; - } - + .chatbox-header { padding: 10px; background-color: #f1f1f1; @@ -110,19 +106,16 @@ padding: 5px 10px; } #leftSidebar, .vscode-web, #rightSidebar { - height: 100vh; + height: 100%; overflow: auto; } - #dragbar, #dragbar2 { - background-color: #ccc; - cursor: col-resize; - } + /* 主容器,使用 grid 布局 */ #maxcontainer { display: grid; grid-template-columns: 1fr 5px 3fr 5px 1fr; /* 默认宽度比例 */ - height: 100vh; + height: 100%; } /* 各区域基础样式 */ @@ -133,22 +126,24 @@ .vscode-web { width: 100%; height: 100%; - border: none; } + .resizer { background-color: #ccc; cursor: col-resize; - width: 5px; /* 设置拖动条宽度 */ + width: 5px; } - + + + .gutter { background-color: #ccc; /* 分隔条颜色 */ cursor: col-resize; }#container { display: grid; grid-template-columns: 1fr 5px 3fr 5px 1fr; /* 初始比例 */ - height: 100vh; + height: 100%; } #leftSidebar, #vscodeWeb, #rightSidebar { @@ -174,13 +169,18 @@ } .progress-title { - height: 60px; /* 每个进度节点的高度 */ + height: 60px; text-align: center; - line-height: 60px; /* 垂直居中 */ - margin: 2px; /* 水平方向上的间隔 */ + line-height: 60px; + margin-right: 2px; border: 1px solid #ddd; transition: background-color 0.3s ease; position: relative; + + /* 新增以下属性实现文字溢出省略 */ + white-space: nowrap; /* 防止文字换行 */ + overflow: hidden; /* 隐藏溢出内容 */ + text-overflow: ellipsis; /* 溢出部分显示为省略号 */ } @@ -295,4 +295,42 @@ } .toggle-button .icon { font-size: 20px; -} \ No newline at end of file +} + + + + + + + + + + + +/* 设置滚动条的宽度、颜色等 */ +-webkit-scrollbar { + width: 8px; /* 初始滚动条宽度 */ + height: 8px; /* 水平滚动条宽度 */ +} + +/* 设置滚动条轨道的颜色 */ +-webkit-scrollbar-track { + background: #f1f1f1; +} + +/* 设置滚动条滑块的颜色 */ +-webkit-scrollbar-thumb { + background: #888; + border-radius: 10px; +} + +/* 当鼠标悬停在滚动条上时,改变其宽度和颜色 */ +-webkit-scrollbar:hover { + width: 12px; /* 鼠标悬停时变粗 */ + height: 12px; +} + +/* 滚动条滑块的颜色变化 */ +-webkit-scrollbar-thumb:hover { + background: #555; /* 鼠标悬停时变暗 */ +} diff --git a/Html/apps/static/js/dashboard.js b/Html/apps/static/js/dashboard.js index e1a059a..80847fa 100644 --- a/Html/apps/static/js/dashboard.js +++ b/Html/apps/static/js/dashboard.js @@ -1,120 +1,133 @@ // dashboard.js let courseData; function show_user_data(user_data, course_brief_data_list){ - courseData = user_data.course_process_dict + courseData = {} + for (let i=0;i -
-

${course.title}

-

${course.course_description}

-
- - `; - courseCardsContainer.appendChild(courseCard); - courseCard.data = course_id - courseCard.addEventListener('click',() => { - openCourseProgress(courseCard.data); - }); - } } function openCourseProgress(courseId) { console.log(courseId) const course = courseData[courseId]; if (!course) return; + fetch(`/dashboard/get_course_progress`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({course_id: courseId}) + }).then(response => response.json()) + .then(data => { + console.log(data) + /* + course_last_study_date: "2025-09-06" + course_put_in_date: "2025-09-06" + course_total_study_time:0 + lesson_processs:Array(1): + (3) [Array(0), '基础算法', '二分'] + material_id:"68bacdfadf5aeae0912f7f18" + user_uuid: $binary: {base64: 'r4hz5J1JSWe1hKJqXnAgwQ==', subType: '04'} - // 更新右侧滑出卡片的内容 - document.getElementById('course-title').textContent = course.title; - ///////////////////////// course.progress;还没有计算 - document.getElementById('progress').textContent = course.progress; + */ + course.course_last_study_date = data.data.course_last_study_date; + course.course_put_in_date = data.data.course_put_in_date; + course.course_total_study_time = data.data.course_total_study_time; + course.lesson_processs = data.data.lesson_processs; + course.material_id = data.data.material_id; + course.user_uuid = data.data.user_uuid; + document.getElementById('course-title').textContent = course.title; + ///////////////////////// course.progress;还没有计算 + document.getElementById('progress').textContent = course.progress; + // 更新章节列表 + const chapterList = document.getElementById('chapter-list'); + chapterList.innerHTML = ''; // 清空之前的章节 - // 更新章节列表 - const chapterList = document.getElementById('chapter-list'); - chapterList.innerHTML = ''; // 清空之前的章节 + course.chapters.forEach(chapter => { + const chapterItem = document.createElement('li'); + chapterItem.classList.add('chapter-item'); + + const chapterTitle = document.createElement('div'); + chapterTitle.classList.add('chapter-title'); + chapterTitle.textContent = chapter.chapter_name; + chapterItem.appendChild(chapterTitle); - course.chapters.forEach(chapter => { - const chapterItem = document.createElement('li'); - chapterItem.classList.add('chapter-item'); - - const chapterTitle = document.createElement('div'); - chapterTitle.classList.add('chapter-title'); - chapterTitle.textContent = chapter.lesson_id; - chapterItem.appendChild(chapterTitle); + // 创建子章节 + if (chapter.lessons && chapter.lessons.length > 0) { + const subChapterList = document.createElement('ul'); + subChapterList.classList.add('sub-chapter-list'); - // 创建子章节 - if (chapter.subChapters && chapter.subChapters.length > 0) { - const subChapterList = document.createElement('ul'); - subChapterList.classList.add('sub-chapter-list'); - - chapter.subChapters.forEach(subChapter => { - const subChapterItem = document.createElement('li'); - subChapterItem.classList.add('sub-chapter-item'); - subChapterItem.textContent = subChapter; - console.log('-------------------') - console.log(course) - // 查找学生进度 - for(let i = 0; i < course.lesson_processs.length; i++) { - if (course.lesson_processs[i][1] == chapter.lesson_id) {//确定lesson_id对应正确 - console.log('-------------------') - console.log(course.lesson_processs[i][0]) - lesson_chapters_progress = course.lesson_processs[i][0];//寻找对应的每一个章节的进度 - for (let j=0; j { + const subChapterItem = document.createElement('li');//课时层级 + subChapterItem.classList.add('sub-chapter-item'); + subChapterItem.textContent = subChapter.lesson_name; + console.log('-------------------') + console.log(course) + // 查找学生进度 + for(let i = 0; i < course.lesson_processs.length; i++) { + if (course.lesson_processs[i][2] == subChapter.lesson_name && course.lesson_processs[i][1] == chapter.chapter_name) {//确定lesson_id对应正确 + /////////////////////////////////////////// + //这里之后要做lesson的各个step的进度和得分 + //////////////////////////////////////////// + + console.log('-------------------') + console.log(course.lesson_processs[i][0]) + lesson_chapters_progress = course.lesson_processs[i][0];//寻找对应的每一个章节的进度 + for (let j=0; j { - const courseId = encodeURIComponent(course.course_id); // 对课程名称进行编码 - const url = `/desktop_nouser/${courseId}/${chapter.lesson_id}`; - window.location.href = url; // 跳转到该学习页面 + // 添加点击事件跳转到学习页面 + subChapterItem.addEventListener('click', () => { + const courseId = encodeURIComponent(course._id); // 对课程名称进行编码 + const url = `/desktop_nouser/${courseId}/${chapter.chapter_name}/${subChapter.lesson_name}`; + window.location.href = url; // 跳转到该学习页面 + }); + + subChapterList.appendChild(subChapterItem); }); - subChapterList.appendChild(subChapterItem); - }); + chapterItem.appendChild(subChapterList); - chapterItem.appendChild(subChapterList); + // 添加点击事件切换子章节显示 + chapterItem.addEventListener('click', () => { + chapterItem.classList.toggle('open'); + }); + } - // 添加点击事件切换子章节显示 - chapterItem.addEventListener('click', () => { - chapterItem.classList.toggle('open'); - }); - } - - chapterList.appendChild(chapterItem); + chapterList.appendChild(chapterItem); + }); + }) + .catch(error => { + console.error('Error:', error); }); + + + // 打开滑出卡片 document.getElementById('courseProgress').classList.add('open'); } diff --git a/Html/apps/static/二分.html b/Html/apps/static/二分.html new file mode 100644 index 0000000..9430bed --- /dev/null +++ b/Html/apps/static/二分.html @@ -0,0 +1,16 @@ + + + + + + + +

b'# \xe4\xba\x8c\xe5\x88\x86\n> Auto-generated at 2025-09-05 19:48\n\n## \xe9\x98\xb6\xe6\xae\xb5\xe4\xb8\x80\xef\xbc\x9a\xe7\xa4\xba\xe4\xbe\x8b\xe9\x98\xb6\xe6\xae\xb5\n### \xe5\xbc\x95\xe5\x85\xa5\n\n\xe4\xba\x8c\xe5\x88\x86\xe6\x98\xaf\xe4\xb8\x80\xe4\xb8\xaa\xe5\xbe\x88\xe7\xae\x80\xe5\x8d\x95\xe5\x9f\xba\xe7\xa1\x80\xef\xbc\x8c\xe4\xbd\x86\xe5\xbe\x88\xe9\x87\x8d\xe8\xa6\x81\xe7\x9a\x84\xe7\x9f\xa5\xe8\xaf\x86\xe7\x82\xb9\xef\xbc\x8c\xe4\xb8\xba\xe4\xbb\xa5\xe5\x90\x8e\xe8\xae\xb8\xe5\xa4\x9a\xe9\xab\x98\xe7\xba\xa7\xe7\x9a\x84\xe6\x95\xb0\xe6\x8d\xae\xe7\xbb\x93\xe6\x9e\x84\xe4\xb8\x8e\xe7\xae\x97\xe6\xb3\x95\xe9\x93\xba\xe5\x9e\xab\xe3\x80\x82\n\n\xe4\xb8\x8b\xe9\x9d\xa2\xe6\x98\xaf\xe4\xb8\x80\xe4\xb8\xaa\xe7\x94\xa8\xe4\xba\x8c\xe5\x88\x86\xe7\x9a\x84\xe7\xae\x80\xe5\x8d\x95\xe5\x9c\xba\xe6\x99\xaf\xef\xbc\x9a\n\n\xe5\x81\x87\xe8\xae\xbe\xe5\xb0\x8f\xe6\x98\x8e\xe4\xbb\x8e0\xe5\x88\xb01000\xe4\xb9\x8b\xe9\x97\xb4\xe9\x80\x89\xe6\x8b\xa9\xe4\xba\x86\xe4\xb8\x80\xe4\xb8\xaa\xe6\x95\xb0\xe5\xad\x97\xe4\xbd\x86\xe4\xb8\x8d\xe5\x91\x8a\xe8\xaf\x89\xe4\xbd\xa0\xef\xbc\x8c\xe4\xbd\xa0\xe5\x8f\xaf\xe4\xbb\xa5\xe4\xb8\x8d\xe6\x96\xad\xe7\x8c\x9c\xe6\xb5\x8b\xe8\xbf\x99\xe4\xb8\xaa\xe6\x95\xb0\xef\xbc\x8c\xe6\xaf\x8f\xe6\xac\xa1\xe7\x8c\x9c\xe6\xb5\x8b\xe5\xb0\x8f\xe6\x98\x8e\xe4\xbc\x9a\xe5\x91\x8a\xe7\x9f\xa5\xe4\xbd\xa0\xe7\x9a\x84\xe7\x8c\x9c\xe6\xb5\x8b\xe5\xbe\x97\xe8\xbf\x87\xe5\xa4\xa7\xe8\xbf\x98\xe6\x98\xaf\xe8\xbf\x87\xe5\xb0\x8f\xef\xbc\x8c\xe9\x97\xae\xe6\x9c\x80\xe5\xa4\x9a\xe5\x87\xa0\xe6\xac\xa1\xe5\xb0\xb1\xe4\xb8\x80\xe5\xae\x9a\xe8\x83\xbd\xe7\x8c\x9c\xe4\xb8\xad\xef\xbc\x9f\n\n\xe7\xad\x94\xe6\xa1\x88\xe6\x98\xaf\xe5\x88\xa9\xe7\x94\xa8\xe4\xba\x8c\xe5\x88\x86\xe6\x9f\xa5\xe6\x89\xbe\xe7\x9a\x84\xe5\x8e\x9f\xe7\x90\x86\xef\xbc\x8c\xe7\x8c\x9c\xe6\xb5\x8b11\xe6\xac\xa1\xe5\x8d\xb3\xe5\x8f\xaf\xe3\x80\x82\n\n1. \xe5\xaf\xb9\xe4\xba\x8e0\xe5\x88\xb01000\xe7\x9a\x84\xe7\xad\x94\xe6\xa1\x88\xe5\xa4\x87\xe9\x80\x89\xe5\x8c\xba\xef\xbc\x8c\xe7\x8c\x9c\xe6\xb5\x8b\xe4\xb8\xad\xe4\xbd\x8d\xe6\x95\xb0500\xef\xbc\x8c\xe5\x81\x87\xe8\xae\xbe\xe8\xbf\x87\xe5\xb0\x8f\xef\xbc\x8c\n2. \xe5\x88\x99\xe5\xaf\xb9\xe4\xba\x8e501\xe5\x88\xb01000\xe7\x9a\x84\xe7\xad\x94\xe6\xa1\x88\xe5\xa4\x87\xe9\x80\x89\xe5\x8c\xba\xef\xbc\x8c\xe7\x8c\x9c\xe6\xb5\x8b750\xef\xbc\x8c\xe5\x81\x87\xe8\xae\xbe\xe8\xbf\x87\xe5\xa4\xa7\n3. \xe5\x88\x99\xe5\xaf\xb9\xe4\xba\x8e501\xe5\x88\xb0749\xe7\x9a\x84\xe7\xad\x94\xe6\xa1\x88\xe5\xa4\x87\xe9\x80\x89\xe5\x8c\xba\xef\xbc\x8c\xe7\x8c\x9c\xe6\xb5\x8b625\xef\xbc\x8c\xe5\x81\x87\xe8\xae\xbe\xe8\xbf\x87\xe5\xb0\x8f\xef\xbc\x8c\n4. \xe5\x88\x99\xe5\xaf\xb9\xe4\xba\x8e626\xe5\x88\xb0749\xe5\x8c\xba\xe9\x97\xb4......\n5. \xef\xbc\x88688-749\xef\xbc\x89\n6. \xef\xbc\x88718-749\xef\xbc\x89\n7. \xef\xbc\x88734-749\xef\xbc\x89\n8. \xef\xbc\x88742-749\xef\xbc\x89\n9. \xef\xbc\x88746-749\xef\xbc\x89\n10. \xef\xbc\x88748-749\xef\xbc\x89\n11. \xef\xbc\x88749-749\xef\xbc\x89\n\n\xe5\x9c\xa8\xe6\x9c\x80\xe5\xb7\xae\xe7\x9a\x84\xe6\x83\x85\xe5\x86\xb5\xe4\xb8\x8b\xef\xbc\x8c\xe7\xac\xac11\xe6\xac\xa1\xe7\x9a\x84\xe7\xad\x94\xe6\xa1\x88\xe5\xa4\x87\xe9\x80\x89\xe5\x8c\xba\xe5\xb0\xb1\xe4\xb8\x80\xe5\xae\x9a\xe9\x95\xbf\xe5\xba\xa6\xe4\xb8\xba1\xe4\xba\x86\xef\xbc\x8c\xe4\xb9\x9f\xe5\xb0\xb1\xe6\x98\xaf\xe5\xbf\x85\xe7\x84\xb6\xe6\x98\xaf\xe7\xad\x94\xe6\xa1\x88\xe3\x80\x82\n\n\xe5\x9b\xa0\xe6\xad\xa4\xe5\xa6\x82\xe6\x9e\x9c\xe5\xba\x8f\xe5\x88\x97\xe6\x98\xaf\xe6\x9c\x89\xe5\xba\x8f\xe7\x9a\x84\xef\xbc\x8c\xe5\xb0\xb1\xe5\x8f\xaf\xe4\xbb'

+ + + \ No newline at end of file diff --git a/Html/apps/static/基础算法-二分.html b/Html/apps/static/基础算法-二分.html new file mode 100644 index 0000000..f1ff381 --- /dev/null +++ b/Html/apps/static/基础算法-二分.html @@ -0,0 +1,56 @@ + + + + + + + +

二分

+
+

Auto-generated at 2025-09-05 19:48

+
+

阶段一:示例阶段

+

引入

+

二分是一个很简单基础,但很重要的知识点,为以后许多高级的数据结构与算法铺垫。

+

下面是一个用二分的简单场景:

+

假设小明从0到1000之间选择了一个数字但不告诉你,你可以不断猜测这个数,每次猜测小明会告知你的猜测得过大还是过小,问最多几次就一定能猜中?

+

答案是利用二分查找的原理,猜测11次即可。

+
    +
  1. 对于0到1000的答案备选区,猜测中位数500,假设过小,
  2. +
  3. 则对于501到1000的答案备选区,猜测750,假设过大
  4. +
  5. 则对于501到749的答案备选区,猜测625,假设过小,
  6. +
  7. 则对于626到749区间......
  8. +
  9. (688-749)
  10. +
  11. (718-749)
  12. +
  13. (734-749)
  14. +
  15. (742-749)
  16. +
  17. (746-749)
  18. +
  19. (748-749)
  20. +
  21. (749-749)
  22. +
+

在最差的情况下,第11次的答案备选区就一定长度为1了,也就是必然是答案。

+

因此如果序列是有序的,就可以通过二分查找快速定位所需要的数据。

+

思考题(询问Agent以学习计算方法,或验证你的答案)

+

对于上面那个题目,如果问题区间是1到4000,最差情况下需要猜测几次?

+

练习:二分查找

+

试试对于下面的题目,用代码实现一下二分查找。

+

题目:有序数组寻址

+

给出一个长度为n的有序数组(从小到大),有q次询问,对于每次询问,输出指定数在数组中的下标。如果不存在则输出-1。

+
输入
+

第一行一个整数n。(1<=n<=10^5)

+

第二行n个用空格分开的整数ai。(0<=ai<=10^8)

+

第三行一个整数q,表示询问的次数。(1<=q<=10^4)

+

后q行,每行一个整数b,表示询问的数。(0<=b<=10^8)

+
输出
+

q行,每行一个整数,对应每次询问的返回结果。

+
提示:
+

完成代码后,通知Agent进行评测。

+

如果你还不完全会这个算法,询问Agent获取提示并进行学习。

+ + + \ No newline at end of file diff --git a/Html/apps/templates/dashboard.html b/Html/apps/templates/dashboard.html index b5bee8a..91f5423 100644 --- a/Html/apps/templates/dashboard.html +++ b/Html/apps/templates/dashboard.html @@ -31,6 +31,13 @@ + {% for course in user_selected_course %} +
+ 课程封面 +
+

{{course.name}}

+

{{course.description}}

+ {% endfor %}
@@ -52,13 +59,14 @@ diff --git a/Html/apps/templates/desktop.html b/Html/apps/templates/desktop.html index 2d01fc5..eafc1d0 100644 --- a/Html/apps/templates/desktop.html +++ b/Html/apps/templates/desktop.html @@ -17,10 +17,10 @@ -
+