新增功能
-
可新增学生
-
可切换当前学生
-
每个学生有独立:
- 历史记录
- 错题本
- 学习报告
- 学习建议
后端:数据库新增学生表
1)修改 backend/app/models.py
新增 Student 模型
from sqlalchemy import Column, Integer, Text, Boolean, ForeignKey
from sqlalchemy.orm import relationship
from app.database import Base
class Student(Base):
__tablename__ = "student"
id = Column(Integer, primary_key=True, index=True)
name = Column(Text, nullable=False)
修改 QuestionHistory,增加学生字段
在 QuestionHistory 里新增这一行:
student_id = Column(Integer, ForeignKey("student.id"), nullable=False, default=1)
2)因为改表结构了,开发阶段最省事还是删库重建
在 backend 目录执行:
rm math_tutor.db
后端:schema 新增学生相关结构
修改 backend/app/schemas.py
新增:
class StudentItem(BaseModel):
id: int
name: str
class Config:
from_attributes = True
class CreateStudentRequest(BaseModel):
name: str
后端:新增学生服务
新增 backend/app/student_service.py
from sqlalchemy.orm import Session
from app.models import Student
def ensure_default_student(db: Session):
row = db.query(Student).filter(Student.id == 1).first()
if not row:
row = Student(id=1, name="默认学生")
db.add(row)
db.commit()
db.refresh(row)
return row
def list_students(db: Session):
ensure_default_student(db)
return db.query(Student).order_by(Student.id.asc()).all()
def create_student(db: Session, name: str):
row = Student(name=name)
db.add(row)
db.commit()
db.refresh(row)
return row
后端:所有查询改为按学生过滤
修改 backend/app/main.py
1)补充 import
新增:
from fastapi import Query
from app.models import QuestionHistory, Student
from app.student_service import ensure_default_student, list_students, create_student
from app.schemas import StudentItem, CreateStudentRequest
2)启动时确保默认学生存在
在 Base.metadata.create_all(bind=engine) 后面加:
from app.database import SessionLocal
with SessionLocal() as db:
ensure_default_student(db)
3)新增学生接口
把下面两个接口加到 main.py 里:
@app.get("/api/students", response_model=list[StudentItem])
def get_students(db: Session = Depends(get_db)):
rows = list_students(db)
return [StudentItem.model_validate(row) for row in rows]
@app.post("/api/students", response_model=StudentItem)
def add_student(req: CreateStudentRequest, db: Session = Depends(get_db)):
row = create_student(db, req.name.strip())
return StudentItem.model_validate(row)
4)修改 /api/solve
把函数签名改成:
def solve_question(
req: SolveQuestionRequest,
student_id: int = Query(1),
db: Session = Depends(get_db)
):
在 QuestionHistory(...) 里新增:
student_id=student_id,
5)修改 /api/solve-image
把函数签名改成:
async def solve_image(
file: UploadFile = File(...),
student_id: int = Query(1),
db: Session = Depends(get_db)
):
在 QuestionHistory(...) 里新增:
student_id=student_id,
6)修改 /api/history
把函数签名改成:
def get_history(
student_id: int = Query(1),
db: Session = Depends(get_db)
):
查询改成:
rows = (
db.query(QuestionHistory)
.filter(QuestionHistory.student_id == student_id)
.order_by(QuestionHistory.id.desc())
.all()
)
7)修改 /api/wrong-questions
把函数签名改成:
def get_wrong_questions(
student_id: int = Query(1),
db: Session = Depends(get_db)
):
查询改成:
rows = (
db.query(QuestionHistory)
.filter(
QuestionHistory.student_id == student_id,
QuestionHistory.is_wrong == True
)
.order_by(QuestionHistory.id.desc())
.all()
)
8)修改 /api/report
把函数签名改成:
def get_learning_report(
student_id: int = Query(1),
db: Session = Depends(get_db)
):
调用改成:
result = build_learning_report(db, student_id)
9)修改 /api/study-suggestion
把函数签名改成:
def get_study_suggestion(
student_id: int = Query(1),
db: Session = Depends(get_db)
):
调用改成:
result = build_study_suggestion(db, student_id)
五、后端:统计服务按学生过滤
修改 backend/app/report_service.py
把函数签名改成:
def build_learning_report(db: Session, student_id: int):
查询改成:
rows = (
db.query(QuestionHistory)
.filter(QuestionHistory.student_id == student_id)
.order_by(QuestionHistory.id.desc())
.all()
)
修改 backend/app/suggestion_service.py
把函数签名改成:
def build_study_suggestion(db: Session, student_id: int):
查询改成:
rows = db.query(QuestionHistory).filter(QuestionHistory.student_id == student_id).all()
前端:API 增加 student_id 参数
修改 frontend/src/api/math.ts
新增学生类型
export interface StudentItem {
id: number
name: string
}
新增学生接口
export function getStudentList() {
return request.get<StudentItem[]>('/api/students')
}
export function createStudent(name: string) {
return request.post<StudentItem>('/api/students', { name })
}
修改这些请求方法签名
solveMathQuestion
改成:
export function solveMathQuestion(data: SolveRequest, student_id: number) {
return request.post<SolveResponse>('/api/solve', data, {
params: { student_id },
})
}
solveMathImage
改成:
export function solveMathImage(file: File, student_id: number) {
const formData = new FormData()
formData.append('file', file)
return request.post<OCRSolveResponse>('/api/solve-image', formData, {
params: { student_id },
headers: {
'Content-Type': 'multipart/form-data',
},
})
}
getHistoryList
改成:
export function getHistoryList(student_id: number) {
return request.get<HistoryItem[]>('/api/history', {
params: { student_id },
})
}
getWrongQuestionList
改成:
export function getWrongQuestionList(student_id: number) {
return request.get<HistoryItem[]>('/api/wrong-questions', {
params: { student_id },
})
}
getLearningReport
改成:
export function getLearningReport(student_id: number) {
return request.get<LearningReportResponse>('/api/report', {
params: { student_id },
})
}
getStudySuggestion
改成:
export function getStudySuggestion(student_id: number) {
return request.get<StudySuggestionResponse>('/api/study-suggestion', {
params: { student_id },
})
}
前端:页面增加学生切换状态
修改 frontend/src/App.vue
1)补充 import
import {
solveMathQuestion,
solveMathImage,
getHistoryList,
getWrongQuestionList,
markWrongQuestion,
generatePracticeByKnowledge,
regenerateQuestion,
getLearningReport,
getStudySuggestion,
getStudentList,
createStudent,
type SolveResponse,
type HistoryItem,
type PracticeQuestionItem,
type LearningReportResponse,
type StudySuggestionResponse,
type StudentItem,
} from './api/math'
2)新增状态
const studentList = ref<StudentItem[]>([])
const currentStudentId = ref(1)
const newStudentName = ref('')
3)新增方法
const loadStudents = async () => {
const { data } = await getStudentList()
studentList.value = data
if (!data.find(item => item.id === currentStudentId.value) && data.length) {
currentStudentId.value = data[0].id
}
}
const handleCreateStudent = async () => {
const name = newStudentName.value.trim()
if (!name) return
try {
const { data } = await createStudent(name)
newStudentName.value = ''
await loadStudents()
currentStudentId.value = data.id
await refreshAllStudentData()
} catch (error: any) {
console.error('创建学生失败:', error)
alert(error?.response?.data?.detail || '创建学生失败')
}
}
const refreshAllStudentData = async () => {
await Promise.all([
loadHistory(),
loadWrongList(),
loadReport(),
loadStudySuggestion(),
])
}
const handleStudentChange = async () => {
result.value = null
practiceList.value = []
regeneratedMap.value = {}
await refreshAllStudentData()
}
4)修改这些方法里的调用
loadHistory
改成:
const loadHistory = async () => {
const { data } = await getHistoryList(currentStudentId.value)
historyList.value = data
}
loadWrongList
改成:
const loadWrongList = async () => {
const { data } = await getWrongQuestionList(currentStudentId.value)
wrongList.value = data
}
loadReport
改成:
const loadReport = async () => {
reportLoading.value = true
try {
const { data } = await getLearningReport(currentStudentId.value)
learningReport.value = data
} catch (error: any) {
console.error('加载学习报告失败:', error)
alert(error?.response?.data?.detail || '加载学习报告失败')
} finally {
reportLoading.value = false
}
}
loadStudySuggestion
改成:
const loadStudySuggestion = async () => {
suggestionLoading.value = true
try {
const { data } = await getStudySuggestion(currentStudentId.value)
studySuggestion.value = data
} catch (error: any) {
console.error('加载学习建议失败:', error)
alert(error?.response?.data?.detail || '加载学习建议失败')
} finally {
suggestionLoading.value = false
}
}
handleSubmit
改这里:
const { data } = await solveMathQuestion(
{ question: currentQuestion },
currentStudentId.value
)
handleImageChange
改这里:
const { data } = await solveMathImage(file, currentStudentId.value)
八、前端:页面顶部增加学生切换区
修改 frontend/src/App.vue
在 <h1>AI 数学辅导老师</h1> 下面插入:
<div class="student-bar">
<select
v-model="currentStudentId"
class="student-select"
@change="handleStudentChange"
>
<option v-for="item in studentList" :key="item.id" :value="item.id">
{{ item.name }}
</option>
</select>
<input
v-model="newStudentName"
class="student-input"
placeholder="输入新学生姓名"
/>
<button class="retry-btn" @click="handleCreateStudent">
新增学生
</button>
</div>
前端:初始化时先加载学生
修改 onMounted
改成:
onMounted(async () => {
await loadStudents()
await refreshAllStudentData()
})
前端样式补充
修改 frontend/src/App.vue
在 style scoped 里新增:
.student-bar {
display: flex;
gap: 12px;
align-items: center;
margin-bottom: 20px;
}
.student-select,
.student-input {
height: 40px;
padding: 0 12px;
border: 1px solid #ddd;
border-radius: 8px;
background: #fff;
outline: none;
}
启动看效果
1)删库重建
cd backend
rm math_tutor.db
2)重启后端
uvicorn app.main:app --reload --port 8000
3)重启前端
npm run dev