12. 开发学生成绩模块
SQL
CREATE TABLE `grade` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
`student_id` int DEFAULT NULL COMMENT '学生ID',
`course_id` int DEFAULT NULL COMMENT '课程ID',
`score` int DEFAULT NULL COMMENT '成绩',
`ispass` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '是否及格',
`time` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='成绩信息';
学生选课的查询接口
# 查询所有数据
@router.get('/selectAll')
async def select_all(studentId :int = 0, status: str = ""):
query = StudentCourse.all().prefetch_related("course")
if studentId > 0:
query = query.filter(student__id=studentId)
if status != "":
query = query.filter(status=status)
student_course_list = await query
student_course_dict_list = [
{
**StudentCoursePydantic.model_validate(student_course).model_dump(),
"courseId": student_course.course.id if student_course.course else None,
"courseName": student_course.course.name if student_course.course else None
}
for student_course in student_course_list
]
return Result.success(student_course_dict_list)
给学生加上学分的字段
CREATE TABLE `student` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '账号',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '密码',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '名称',
`avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '头像',
`role` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '角色',
`clazz_id` int DEFAULT NULL COMMENT '班级',
`score` int DEFAULT NULL COMMENT '学分',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `username` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='学生信息';

注意登录的返回模型 加上 score 字段
class Account(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int | None = None
username: str | None = None
password: str | None = None
newPassword: str | None = None
role: str | None = None
name: str | None = None
avatar: str | None = None
clazzId: int | None = None
majorId: int | None = None
score: int | None = None
退课 更新状态
# 更新
@router.put("/update")
async def add(student_course_create_pydantic: StudentCourseCreatePydantic):
if student_course_create_pydantic.id is None:
raise CustomException("缺少参数ID")
if student_course_create_pydantic.status == '已退':
grade = await (Grade.filter(student_id=student_course_create_pydantic.student_id)
.filter(course_id=student_course_create_pydantic.course_id).first())
if grade is not None:
raise CustomException('当前课程已打分,无法退课')
if student_course_create_pydantic.checkStatus == '通过':
student_course_create_pydantic.status = '已选'
elif student_course_create_pydantic.checkStatus == '拒绝':
student_course_create_pydantic.status = '未选中'
# 将参数转换成 字典数据
update_data = student_course_create_pydantic.model_dump(exclude_unset=True, exclude={"id"})
await StudentCourse.filter(id=student_course_create_pydantic.id).update(
**update_data) # no=xxx,name=xxx,college=xxx where id = xxx
return Result.success()
学生成绩后台接口
from datetime import datetime
from typing import Optional
from fastapi import APIRouter
from pydantic import create_model, BaseModel, Field
from tortoise.contrib.pydantic import pydantic_model_creator
from tortoise.expressions import F
from common.exception_handler import CustomException
from common.result import Result, PageInfo
from models import Grade, Student, Course
router = APIRouter(prefix="/grade")
GradePydantic = pydantic_model_creator(Grade)
GradeCreatePydantic = create_model(
"GradeCreatePydantic",
**{
name: (Optional[field.annotation], None)
for name, field in GradePydantic.model_fields.items()
},
student_id=(Optional[int], Field(None, alias="studentId")),
course_id=(Optional[int], Field(None, alias="courseId"))
)
# 新增
@router.post("/add")
async def add(grade_create_pydantic: GradeCreatePydantic):
# 当前的这个学生是否有已选的课程
db_grade = await (Grade.filter(student_id=grade_create_pydantic.student_id)
.filter(course_id=grade_create_pydantic.course_id)
.first())
if db_grade is not None:
raise CustomException("该学生课程成绩已被录入")
grade_create_pydantic.time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 将参数转换成 字典数据
create_data = grade_create_pydantic.model_dump(exclude_unset=True, exclude={"id"})
await Grade.create(**create_data) # no=xxx,name=xxx,college=xxx
if grade_create_pydantic.ispass == '是':
# 查询课程的学分
course = await Course.filter(id=grade_create_pydantic.course_id).first()
add_score = course.score
# 设置学分
await Student.filter(id=grade_create_pydantic.student_id).update(score=F('score') + add_score)
return Result.success()
# 更新
@router.put("/update")
async def add(grade_create_pydantic: GradeCreatePydantic):
if grade_create_pydantic.id is None:
raise CustomException("缺少参数ID")
# 将参数转换成 字典数据
update_data = grade_create_pydantic.model_dump(exclude_unset=True, exclude={"id"})
await Grade.filter(id=grade_create_pydantic.id).update(**update_data) # no=xxx,name=xxx,college=xxx where id = xxx
return Result.success()
# 删除
@router.delete('/delete/{grade_id}')
async def delete(grade_id: int):
await Grade.filter(id=grade_id).delete()
return Result.success()
# 单个查询
@router.get('/selectById/{grade_id}')
async def select_by_id(grade_id: int):
grade = await Grade.get_or_none(id=grade_id)
return Result.success(grade)
# 查询所有数据
@router.get('/selectAll')
async def select_all(name: str = ""):
query = Grade.all()
grade_list = await query.filter(name__contains=name)
return Result.success(grade_list)
# 分页查询数据
@router.get('/selectPage')
async def select_page(studentId: int = 0, studentName: str = "", courseName: str = "", pageNum: int = 1, pageSize: int = 10):
# name__contains表示根据name进行模糊查询 prefetch_related 关联查询到 major模块的数据
query = Grade.all().prefetch_related("student", "course")
if studentId > 0:
query = query.filter(student__id=studentId)
if studentName != "":
query = query.filter(student__name__contains=studentName)
if courseName != "":
query = query.filter(course__name__contains=courseName)
grade_list = await query.order_by("-id").offset((pageNum - 1) * pageSize).limit(pageSize)
total = await query.count()
grade_dict_list = [
{
**GradePydantic.model_validate(grade).model_dump(), # id=xxx,no=xxx,name=xxx
"studentName": grade.student.name if grade.student else None,
"courseName": grade.course.name if grade.course else None
}
for grade in grade_list
]
page_info = PageInfo(list=grade_dict_list, total=total)
return Result.success(page_info)
学生成绩页面
<template>
<div>
<div class="card" style="margin-bottom: 5px;">
<el-input v-model="data.courseName" style="width: 300px; margin-right: 10px" placeholder="请输入课程名称查询"></el-input>
<el-input v-if="data.user.role === '管理员'" v-model="data.studentName" style="width: 300px; margin-right: 10px" placeholder="请输入学生名称查询"></el-input>
<el-button type="primary" @click="load">查询</el-button>
<el-button type="info" style="margin: 0 10px" @click="reset">重置</el-button>
</div>
<div class="card" style="margin-bottom: 5px">
<div style="margin-bottom: 10px" v-if="data.user.role === '管理员'">
<el-button type="primary" @click="handleAdd" >新增</el-button>
</div>
<el-table :data="data.tableData" stripe>
<el-table-column label="课程名称" prop="courseName"></el-table-column>
<el-table-column label="学生名称" prop="studentName"></el-table-column>
<el-table-column label="分数" prop="score"></el-table-column>
<el-table-column label="是否及格" prop="ispass">
<template #default="scope">
<b style="color: #1abc00" v-if="scope.row.ispass === '是'">是</b>
<b style="color: red" v-if="scope.row.ispass === '否'">否</b>
</template>
</el-table-column>
<el-table-column label="创建时间" prop="time"></el-table-column>
<el-table-column label="操作" align="center" width="160" v-if="data.user.role === '管理员'">
<template #default="scope">
<el-button type="primary" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="card">
<el-pagination @current-change="load" background layout="total, prev, pager, next" v-model:page-size="data.pageSize" v-model:current-page="data.pageNum" :total="data.total"/>
</div>
<el-dialog title="成绩信息" width="40%" v-model="data.formVisible" :close-on-click-modal="false" destroy-on-close>
<el-form ref="formRef" :model="data.form" :rules="data.rules" label-width="100px" style="padding-right: 50px">
<el-form-item label="学生" prop="studentId" v-if="!data.form.id">
<el-select placeholder="请选择学生" v-model="data.form.studentId" @change="selectCourse">
<el-option v-for="item in data.studentList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="课程" prop="courseId" v-if="!data.form.id">
<el-select :disabled="!data.form.studentId" placeholder="请选择课程" v-model="data.form.courseId">
<el-option v-for="item in data.studentCourseList" :key="item.id" :label="item.courseName" :value="item.courseId"></el-option>
</el-select>
</el-form-item>
<el-form-item label="成绩" prop="score">
<el-input-number style="width: 200px" :min="1" placeholder="请输入成绩" v-model="data.form.score" autocomplete="off" />
</el-form-item>
<el-form-item label="是否合格" prop="ispass">
<el-radio-group v-model="data.form.ispass">
<el-radio-button label="是" value="是"></el-radio-button>
<el-radio-button label="否" value="否"></el-radio-button>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="data.formVisible = false">取 消</el-button>
<el-button type="primary" @click="save">保 存</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import request from "@/utils/request";
import {reactive, ref} from "vue";
import {ElMessageBox, ElMessage} from "element-plus";
const formRef = ref()
const data = reactive({
user: JSON.parse(localStorage.getItem('system-user') || '{}'),
pageNum: 1,
pageSize: 10,
total: 0,
formVisible: false,
form: {},
tableData: [],
studentList: [],
studentCourseList: [],
courseName: null,
studentName: null,
rules: {
studentId: [
{ required: true, message: '请选择学生', trigger: 'change' }
],
courseId: [
{ required: true, message: '请选择课程', trigger: 'change' }
],
score: [
{ required: true, message: '请输入成绩', trigger: 'blur' }
],
ispass: [
{ required: true, message: '请选择是否合格', trigger: 'change' }
],
}
})
// 查询学生的信息list
request.get('/student/selectAll').then(res => {
data.studentList = res.data
})
// 查询选课的接口
const selectCourse = () => {
data.form.courseId = null // 先清空课程
request.get('/studentCourse/selectAll', {
params: {
studentId: data.form.studentId,
status: '已选'
}
}).then(res => {
data.studentCourseList = res.data
})
}
// 分页查询
const load = () => {
request.get('/grade/selectPage', {
params: {
pageNum: data.pageNum,
pageSize: data.pageSize,
courseName: data.courseName,
studentName: data.studentName,
studentId: data.user.role === '管理员' ? null : data.user.id,
}
}).then(res => {
if (res.code === '200') {
data.tableData = res.data?.list
data.total = res.data?.total
} else {
ElMessage.error(res.msg)
}
})
}
load()
// 新增
const handleAdd = () => {
data.form = {}
data.formVisible = true
}
// 编辑
const handleEdit = (row) => {
data.form = JSON.parse(JSON.stringify(row))
data.formVisible = true
}
// 新增保存
const add = () => {
request.post('/grade/add', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}
// 编辑保存
const update = () => {
request.put('/grade/update', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}
// 弹窗保存
const save = () => {
formRef.value.validate(valid => {
if (valid) {
// data.form有id就是更新,没有就是新增
data.form.id ? update() : add()
}
})
}
// 删除
const handleDelete = (id) => {
ElMessageBox.confirm('删除后数据无法恢复,您确定删除吗?', '删除确认', { type: 'warning' }).then(res => {
request.delete('/grade/delete/' + id).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
} else {
ElMessage.error(res.msg)
}
})
}).catch(err => {})
}
// 重置
const reset = () => {
data.courseName = null
data.studentName = null
load()
}
</script>