多学生切换,添加学生档案

0 阅读4分钟

新增功能

  • 可新增学生

  • 可切换当前学生

  • 每个学生有独立:

    • 历史记录
    • 错题本
    • 学习报告
    • 学习建议

后端:数据库新增学生表

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

image.png

image.png

image.png