登录接口
# 登录
@api_router.post("/login")
async def login(account: Account):
if account.role == '管理员':
admin = await Admin.get_or_none(username=account.username)
if admin is None:
raise CustomException("账号或密码错误")
if admin.password != account.password:
raise CustomException("账号或密码错误")
account = Account.model_validate(admin)
elif account.role == '学生':
student = await Student.get_or_none(username=account.username).prefetch_related("clazz")
if student is None:
raise CustomException("账号或密码错误")
if student.password != account.password:
raise CustomException("账号或密码错误")
account = Account.model_validate(student)
account.clazzId = student.clazz.id if student and student.clazz else None
else:
raise CustomException("角色错误")
return Result.success(account)
注意 Account 的数据模型
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
注册接口
# 注册
@api_router.post("/register")
async def register(account: Account):
if account.username is None:
raise CustomException("账号不能为空")
if account.password is None:
raise CustomException("密码不能为空")
# 设置默认的name
if account.name is None:
account.name = account.username
student = await Student.get_or_none(username=account.username)
if student is not None:
raise CustomException("账号已存在")
create_data = account.model_dump(exclude_unset=True, exclude={"id"})
await Student.create(**create_data)
return Result.success()
关联查询的时候,关联的条件 必须加上 if 判断

最终学生的分页查询
# 分页查询数据
@router.get('/selectPage')
async def select_all(name: str = "", clazzName: str = "", majorName: str = "", pageNum: int = 1, pageSize: int = 10):
# name__contains表示根据name进行模糊查询 prefetch_related 关联查询到 major模块的数据
query = Student.filter(name__contains=name)
if clazzName and clazzName != "":
query = query.filter(clazz__name__contains=clazzName)
if majorName and clazzName != "":
query = query.filter(clazz__major__name__contains=majorName)
query = query.prefetch_related("clazz__major")
student_list = await query.offset((pageNum - 1) * pageSize).limit(pageSize)
total = await query.count()
# student_list 转成字典数据
# majorName 怎么返回??
# {id=xxx, name=xxx, no=xxx}
student_dict_list = [
{
**StudentPydantic.model_validate(student).model_dump(), # id=xxx,no=xxx,name=xxx
"clazzId": student.clazz.id if student.clazz else None,
"clazzName": student.clazz.name if student.clazz else None,
"majorName": student.clazz.major.name if student.clazz and student.clazz.major else None
}
for student in student_list
]
page_info = PageInfo(list=student_dict_list, total=total)
return Result.success(page_info)
个人信息页面 Person.vue
<template>
<div style="width: 40%">
<div class="card" style="padding: 30px">
<el-form ref="formRef" :model="data.user" :rules="data.rules" label-width="100px" style="padding-right: 50px">
<div style="margin: 20px 0; text-align: center">
<el-upload :show-file-list="false" class="avatar-uploader" :action="uploadUrl" :on-success="handleFileUpload">
<img v-if="data.user.avatar" :src="data.user.avatar" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</div>
<el-form-item label="账号" prop="username">
<el-input disabled v-model="data.user.username" autocomplete="off" />
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="data.user.name" autocomplete="off" />
</el-form-item>
<el-form-item label="所属班级" prop="clazzId">
<el-select placeholder="请选择班级" v-model="data.user.clazzId">
<el-option v-for="item in data.classList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<div style="text-align: center">
<el-button type="primary" @click="save">保存</el-button>
</div>
</el-form>
</div>
</div>
</template>
<script setup>
import {reactive, ref} from "vue"
import request from "@/utils/request";
import {ElMessage} from "element-plus";
// 文件上传的接口地址
const uploadUrl = import.meta.env.VITE_BASE_URL + '/files/upload'
const formRef = ref()
const data = reactive({
user: JSON.parse(localStorage.getItem('system-user') || '{}'),
classList: [],
rules: {
username: [
{required: true, message: '请输入账号', trigger: 'blur'}
],
name: [
{required: true, message: '请输入名称', trigger: 'blur'}
],
}
})
// 查询班级的信息list
request.get('/clazz/selectAll').then(res => {
data.classList = res.data
})
const handleFileUpload = (file) => {
data.user.avatar = file.data
}
const emit = defineEmits(["updateUser"])
// 把当前修改的用户信息存储到后台数据库
const save = () => {
formRef.value.validate(valid => {
if (valid) {
if (data.user.role === '管理员') {
request.put('/admin/update', data.user).then(res => {
if (res.code === '200') {
ElMessage.success('更新成功')
//把更新后的用户信息存储到缓存
localStorage.setItem('system-user', JSON.stringify(data.user))
emit('updateUser')
} else {
ElMessage.error(res.msg)
}
})
}
if (data.user.role === '学生') {
request.put('/student/update', data.user).then(res => {
if (res.code === '200') {
ElMessage.success('更新成功')
//把更新后的用户信息存储到缓存
localStorage.setItem('system-user', JSON.stringify(data.user))
emit('updateUser')
} else {
ElMessage.error(res.msg)
}
})
}
}
})
}
</script>
<style scoped>
.avatar-uploader .avatar {
width: 120px;
height: 120px;
display: block;
}
</style>
<style>
.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
text-align: center;
}
</style>
修改密码接口
# 修改密码
@api_router.put("/updatePassword")
async def update_password(account: Account):
if account.role == '管理员':
admin = await Admin.get_or_none(id=account.id)
if admin is None:
raise CustomException("未找到用户")
if admin.password != account.password:
raise CustomException("原密码错误")
if admin.password == account.newPassword:
raise CustomException("新密码不能原密码跟相同")
await Admin.filter(id=admin.id).update(password=account.newPassword)
if account.role == '学生':
student = await Student.get_or_none(id=account.id)
if student is None:
raise CustomException("未找到用户")
if student.password != account.password:
raise CustomException("原密码错误")
if student.password == account.newPassword:
raise CustomException("新密码不能原密码跟相同")
await Student.filter(id=student.id).update(password=account.newPassword)
return Result.success(account)
Password.vue
<template>
<div style="width: 40%">
<div class="card" style="padding: 30px">
<el-form ref="formRef" :rules="data.rules" :model="data.user" label-width="100px" style="padding-right: 50px">
<el-form-item label="原密码" prop="password">
<el-input v-model="data.user.password" show-password />
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="data.user.newPassword" show-password />
</el-form-item>
<el-form-item label="确认新密码" prop="confirmPassword">
<el-input v-model="data.user.confirmPassword" show-password />
</el-form-item>
<div style="text-align: center">
<el-button type="primary" @click="save">保存</el-button>
</div>
</el-form>
</div>
</div>
</template>
<script setup>
import {reactive, ref} from "vue"
import request from "@/utils/request";
import {ElMessage} from "element-plus";
import router from "@/router";
const formRef = ref()
const data = reactive({
user: JSON.parse(localStorage.getItem('system-user') || '{}'),
rules: {
password: [
{required: true, message: '请输入原密码', trigger: 'blur'}
],
newPassword: [
{required: true, message: '请输入原密码', trigger: 'blur'}
],
confirmPassword: [
{required: true, message: '请输入原密码', trigger: 'blur'}
],
}
})
// 把当前修改的用户信息存储到后台数据库
const save = () => {
formRef.value.validate(valid => {
if (valid) {
if (data.user.password === data.user.newPassword) {
ElMessage.error('新密码不能和原密码一致')
return
}
if (data.user.newPassword !== data.user.confirmPassword) {
ElMessage.error('确认新密码错误')
return
}
request.put('/updatePassword', data.user).then(res => {
if (res.code === '200') {
ElMessage.success('修改密码成功')
//把更新后的用户信息存储到缓存
localStorage.setItem('system-user', JSON.stringify(data.user))
router.push('/login')
} else {
ElMessage.error(res.msg)
}
})
}
})
}
</script>