智教助手平台:完整初始化

- 前端:Vue3 + TS + Element Plus,24 个页面路由(课件/组题/教案/动画/思维导图/作文批改/命题/课堂/资源/社区等)
- 后端:FastAPI + SQLAlchemy + SQLite,17 个路由模块,AI 服务层含降级模板
- AI:glm-5.x 推理模型已禁用思维链,确保输出真实内容
- 修复:ai_service 两处请求体注入 enable_thinking/thinking disabled
- 测试账号:13900999999 / test1234
This commit is contained in:
Zhang Jing Xuan
2026-07-29 16:29:10 +08:00
commit 0841b1a103
193 changed files with 44995 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
from .user import User
from .courseware import Courseware, CoursewareShare
from .animation import Animation
from .exercise import Exercise, ExerciseAttempt
from .lesson_plan import LessonPlan
from .essay import EssayGrade
from .exam import Exam
from .resource import Resource, ResourceFavorite
from .material import Material
from .community import Post, Comment, PostFavorite
from .classroom import ClassroomActivity
from .credit import CreditAccount, CreditTransaction
from .audit import AuditLog
from .mindmap import MindMap
from .reset_code import ResetCode
from .notification import Notification
from .chat import ChatConversation, ChatMessage
__all__ = [
"User", "Courseware", "CoursewareShare", "Animation", "Exercise", "ExerciseAttempt",
"LessonPlan", "EssayGrade", "Exam", "Resource", "ResourceFavorite", "Material", "Post", "Comment", "PostFavorite", "ClassroomActivity",
"CreditAccount", "CreditTransaction",
"AuditLog",
"MindMap",
"ResetCode",
"Notification",
"ChatConversation", "ChatMessage",
]
+24
View File
@@ -0,0 +1,24 @@
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON, ForeignKey, Enum as SAEnum
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import Base
class Animation(Base):
__tablename__ = "animations"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
title = Column(String(200), nullable=False)
anim_type = Column(
SAEnum("math", "physics", "chemistry", "biology", "geography", "general", name="anim_type"),
default="general",
)
description = Column(Text, default="")
config = Column(JSON, default=dict)
thumbnail = Column(String(500), default="")
status = Column(SAEnum("draft", "published", name="anim_status"), default="draft")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
author = relationship("User", back_populates="animations")
+21
View File
@@ -0,0 +1,21 @@
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy.sql import func
from database import Base
class AuditLog(Base):
"""安全审计日志:记录登录、权限变更、内容发布、敏感操作等。"""
__tablename__ = "audit_logs"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, nullable=True, index=True)
username = Column(String(100), default="")
action = Column(String(64), nullable=False, index=True)
target_type = Column(String(32), default="")
target_id = Column(String(64), default="")
detail = Column(Text, default="")
ip = Column(String(64), default="")
user_agent = Column(String(255), default="")
status = Column(String(16), default="success") # success / failed / denied
created_at = Column(DateTime, server_default=func.now())
+39
View File
@@ -0,0 +1,39 @@
from sqlalchemy import Column, Integer, String, DateTime, JSON, ForeignKey, Text, Boolean
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import Base
class ChatConversation(Base):
__tablename__ = "chat_conversations"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
title = Column(String(200), nullable=False, default="新对话")
subject = Column(String(50), default="")
grade = Column(String(50), default="")
pinned = Column(Boolean, default=False)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
author = relationship("User", back_populates="chat_conversations")
messages = relationship(
"ChatMessage",
back_populates="conversation",
order_by="ChatMessage.id",
lazy="selectin",
cascade="all, delete-orphan",
)
class ChatMessage(Base):
__tablename__ = "chat_messages"
id = Column(Integer, primary_key=True, index=True)
conversation_id = Column(Integer, ForeignKey("chat_conversations.id"), nullable=False, index=True)
role = Column(String(20), nullable=False) # user / assistant / system
content = Column(Text, nullable=False)
tokens = Column(Integer, default=0)
created_at = Column(DateTime, server_default=func.now())
conversation = relationship("ChatConversation", back_populates="messages")
+20
View File
@@ -0,0 +1,20 @@
from sqlalchemy import Column, DateTime, ForeignKey, Integer, JSON, String, Text, func
from sqlalchemy.orm import relationship
from database import Base
class ClassroomActivity(Base):
__tablename__ = "classroom_activities"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
title = Column(String(200), nullable=False)
activity_type = Column(String(50), default="poll")
description = Column(Text, default="")
config = Column(JSON, default=dict)
responses = Column(JSON, default=list)
status = Column(String(20), default="draft")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
author = relationship("User")
+54
View File
@@ -0,0 +1,54 @@
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON, ForeignKey, UniqueConstraint
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import Base
class Post(Base):
__tablename__ = "posts"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
title = Column(String(200), nullable=False)
content = Column(Text, default="")
post_type = Column(String(50), default="discussion")
tags = Column(JSON, default=list)
attachments = Column(JSON, default=list)
views = Column(Integer, default=0)
likes = Column(Integer, default=0)
comments_count = Column(Integer, default=0)
is_pinned = Column(Integer, default=0)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
author = relationship("User", back_populates="posts")
comments = relationship("Comment", back_populates="post", lazy="selectin", cascade="all, delete-orphan")
favorites = relationship("PostFavorite", back_populates="post", lazy="selectin", cascade="all, delete-orphan")
class Comment(Base):
__tablename__ = "comments"
id = Column(Integer, primary_key=True, index=True)
post_id = Column(Integer, ForeignKey("posts.id"), nullable=False)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
content = Column(Text, nullable=False)
parent_id = Column(Integer, ForeignKey("comments.id"), nullable=True)
likes = Column(Integer, default=0)
created_at = Column(DateTime, server_default=func.now())
post = relationship("Post", back_populates="comments")
author = relationship("User")
class PostFavorite(Base):
__tablename__ = "post_favorites"
__table_args__ = (UniqueConstraint("user_id", "post_id", name="uq_post_favorite_user_post"),)
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
post_id = Column(Integer, ForeignKey("posts.id"), nullable=False, index=True)
created_at = Column(DateTime, server_default=func.now())
user = relationship("User")
post = relationship("Post", back_populates="favorites")
+46
View File
@@ -0,0 +1,46 @@
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON, ForeignKey, Enum as SAEnum
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import Base
class Courseware(Base):
__tablename__ = "coursewares"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
title = Column(String(200), nullable=False)
subject = Column(String(50), default="")
grade = Column(String(50), default="")
description = Column(Text, default="")
content = Column(JSON, default=list)
cover_image = Column(String(500), default="")
status = Column(SAEnum("draft", "published", "archived", name="cw_status"), default="draft")
version = Column(Integer, default=1)
tags = Column(JSON, default=list)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
author = relationship("User", back_populates="coursewares")
class CoursewareShare(Base):
__tablename__ = "courseware_shares"
id = Column(Integer, primary_key=True, index=True)
courseware_id = Column(Integer, ForeignKey("coursewares.id"), nullable=False, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
token = Column(String(64), unique=True, nullable=False, index=True)
title = Column(String(200), nullable=False)
subject = Column(String(50), default="")
grade = Column(String(50), default="")
description = Column(Text, default="")
content = Column(JSON, default=list)
tags = Column(JSON, default=list)
views = Column(Integer, default=0)
status = Column(String(20), default="active")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
courseware = relationship("Courseware")
author = relationship("User")
+33
View File
@@ -0,0 +1,33 @@
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import Base
class CreditAccount(Base):
__tablename__ = "credit_accounts"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), unique=True, nullable=False, index=True)
balance = Column(Integer, default=130)
total_granted = Column(Integer, default=130)
total_used = Column(Integer, default=0)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
user = relationship("User", back_populates="credit_account")
class CreditTransaction(Base):
__tablename__ = "credit_transactions"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
amount = Column(Integer, nullable=False)
action = Column(String(50), default="")
description = Column(Text, default="")
balance_after = Column(Integer, default=0)
created_at = Column(DateTime, server_default=func.now())
user = relationship("User", back_populates="credit_transactions")
+21
View File
@@ -0,0 +1,21 @@
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import Base
class EssayGrade(Base):
__tablename__ = "essay_grades"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
title = Column(String(200), nullable=False)
essay_text = Column(Text, default="")
grade_level = Column(String(50), default="初中")
essay_type = Column(String(50), default="记叙文")
total_score = Column(Integer, default=50)
result = Column(JSON, default=dict)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
author = relationship("User", back_populates="essay_grades")
+22
View File
@@ -0,0 +1,22 @@
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import Base
class Exam(Base):
__tablename__ = "exams"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
title = Column(String(200), nullable=False)
subject = Column(String(50), default="")
grade = Column(String(50), default="")
questions = Column(JSON, default=list)
answers = Column(JSON, default=list)
duration = Column(Integer, default=90)
total_score = Column(Integer, default=100)
difficulty = Column(String(20), default="medium")
knowledge_points = Column(JSON, default=list)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
+44
View File
@@ -0,0 +1,44 @@
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON, ForeignKey, Enum as SAEnum
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import Base
class Exercise(Base):
__tablename__ = "exercises"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
title = Column(String(200), nullable=False)
exercise_type = Column(
SAEnum(
"game_snake", "game_match", "game_adventure",
"choice", "fill_blank", "true_false", "drag_sort", "matching",
name="exercise_type",
),
default="choice",
)
subject = Column(String(50), default="")
knowledge_points = Column(JSON, default=list)
questions = Column(JSON, default=list)
settings = Column(JSON, default=dict)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
author = relationship("User", back_populates="exercises")
attempts = relationship("ExerciseAttempt", back_populates="exercise", lazy="selectin")
class ExerciseAttempt(Base):
__tablename__ = "exercise_attempts"
id = Column(Integer, primary_key=True, index=True)
exercise_id = Column(Integer, ForeignKey("exercises.id"), nullable=False)
student_name = Column(String(50), default="")
answers = Column(JSON, default=list)
score = Column(Integer, default=0)
total = Column(Integer, default=0)
duration = Column(Integer, default=0)
created_at = Column(DateTime, server_default=func.now())
exercise = relationship("Exercise", back_populates="attempts")
+25
View File
@@ -0,0 +1,25 @@
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import Base
class LessonPlan(Base):
__tablename__ = "lesson_plans"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
title = Column(String(200), nullable=False)
subject = Column(String(50), default="")
grade = Column(String(50), default="")
objectives = Column(JSON, default=list)
key_points = Column(JSON, default=list)
difficulties = Column(JSON, default=list)
content = Column(JSON, default=dict)
duration = Column(Integer, default=45)
materials = Column(JSON, default=list)
homework = Column(JSON, default=list)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
author = relationship("User", back_populates="lesson_plans")
+26
View File
@@ -0,0 +1,26 @@
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON, ForeignKey, Enum as SAEnum
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import Base
class Material(Base):
__tablename__ = "materials"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
filename = Column(String(255), nullable=False)
title = Column(String(200), nullable=False)
material_type = Column(String(50), default="text")
subject = Column(String(50), default="")
grade = Column(String(50), default="")
tags = Column(JSON, default=list)
summary = Column(Text, default="")
char_count = Column(Integer, default=0)
size = Column(Integer, default=0)
source = Column(String(50), default="upload")
status = Column(SAEnum("active", "archived", name="material_status"), default="active")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
author = relationship("User", back_populates="materials")
+18
View File
@@ -0,0 +1,18 @@
from sqlalchemy import Column, Integer, String, DateTime, JSON, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import Base
class MindMap(Base):
__tablename__ = "mind_maps"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
title = Column(String(200), nullable=False)
subject = Column(String(50), default="综合")
nodes = Column(JSON, default=list)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
author = relationship("User", back_populates="mind_maps")
+21
View File
@@ -0,0 +1,21 @@
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import Base
class Notification(Base):
"""站内通知:评论/收藏/系统消息。"""
__tablename__ = "notifications"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
actor_id = Column(Integer, nullable=True)
ntype = Column(String(32), nullable=False, default="comment")
title = Column(String(200), nullable=False)
content = Column(String(500), default="")
link = Column(String(255), default="")
is_read = Column(Boolean, default=False)
created_at = Column(DateTime, server_default=func.now())
user = relationship("User", back_populates="notifications")
+16
View File
@@ -0,0 +1,16 @@
import datetime
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.sql import func
from database import Base
class ResetCode(Base):
"""密码重置验证码:绑定手机号,5 分钟过期。"""
__tablename__ = "reset_codes"
id = Column(Integer, primary_key=True, index=True)
phone = Column(String(20), nullable=False, index=True)
code = Column(String(6), nullable=False)
consumed = Column(Integer, default=0)
expires_at = Column(DateTime, nullable=False)
created_at = Column(DateTime, server_default=func.now())
+46
View File
@@ -0,0 +1,46 @@
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON, ForeignKey, Enum as SAEnum, UniqueConstraint
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import Base
class Resource(Base):
__tablename__ = "resources"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
resource_type = Column(
SAEnum("courseware", "animation", "exercise", "lesson_plan", "exam", "other", name="res_type"),
default="other",
)
title = Column(String(200), nullable=False)
description = Column(Text, default="")
content_ref = Column(String(500), default="")
file_url = Column(String(500), default="")
cover_image = Column(String(500), default="")
tags = Column(JSON, default=list)
subject = Column(String(50), default="")
grade = Column(String(50), default="")
downloads = Column(Integer, default=0)
likes = Column(Integer, default=0)
views = Column(Integer, default=0)
is_public = Column(Integer, default=1)
status = Column(SAEnum("active", "archived", name="res_status"), default="active")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
author = relationship("User", back_populates="resources")
favorites = relationship("ResourceFavorite", back_populates="resource", cascade="all, delete-orphan")
class ResourceFavorite(Base):
__tablename__ = "resource_favorites"
__table_args__ = (UniqueConstraint("user_id", "resource_id", name="uq_resource_favorite_user_resource"),)
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
resource_id = Column(Integer, ForeignKey("resources.id"), nullable=False, index=True)
created_at = Column(DateTime, server_default=func.now())
user = relationship("User", back_populates="resource_favorites")
resource = relationship("Resource", back_populates="favorites")
+36
View File
@@ -0,0 +1,36 @@
from sqlalchemy import Column, Integer, String, DateTime, Enum as SAEnum, Boolean
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
phone = Column(String(20), unique=True, index=True, nullable=False)
password_hash = Column(String(255), nullable=False)
name = Column(String(50), nullable=False)
avatar = Column(String(500), default="")
subject = Column(String(50), default="")
school = Column(String(100), default="")
grade = Column(String(50), default="")
role = Column(SAEnum("teacher", "admin", name="user_role"), default="teacher")
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
coursewares = relationship("Courseware", back_populates="author", lazy="selectin")
animations = relationship("Animation", back_populates="author", lazy="selectin")
exercises = relationship("Exercise", back_populates="author", lazy="selectin")
lesson_plans = relationship("LessonPlan", back_populates="author", lazy="selectin")
essay_grades = relationship("EssayGrade", back_populates="author", lazy="selectin", cascade="all, delete-orphan")
resources = relationship("Resource", back_populates="author", lazy="selectin")
materials = relationship("Material", back_populates="author", lazy="selectin", cascade="all, delete-orphan")
resource_favorites = relationship("ResourceFavorite", back_populates="user", lazy="selectin", cascade="all, delete-orphan")
posts = relationship("Post", back_populates="author", lazy="selectin")
credit_account = relationship("CreditAccount", back_populates="user", uselist=False, lazy="selectin", cascade="all, delete-orphan")
credit_transactions = relationship("CreditTransaction", back_populates="user", lazy="selectin", cascade="all, delete-orphan")
mind_maps = relationship("MindMap", back_populates="author", lazy="selectin", cascade="all, delete-orphan")
notifications = relationship("Notification", back_populates="user", lazy="selectin", cascade="all, delete-orphan")
chat_conversations = relationship("ChatConversation", back_populates="author", lazy="selectin", cascade="all, delete-orphan")