[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-public-CTQ2w9Yp":3,"public-project-articles-CTQ2w9Yp":17},{"id":4,"uuid":5,"project_id":6,"title":7,"content":8,"type":9,"status":10,"public_enabled":10,"views":11,"sort":12,"created_at":13,"updated_at":14,"project_title":15,"project_slug":16},1142,"CTQ2w9Yp",55,"09. 开发学生登录、注册、个人信息、修改密码功能","## 登录接口\n\n```python\n# 登录\n@api_router.post(\"\u002Flogin\")\nasync def login(account: Account):\n    if account.role == '管理员':\n        admin = await Admin.get_or_none(username=account.username)\n        if admin is None:\n            raise CustomException(\"账号或密码错误\")\n        if admin.password != account.password:\n            raise CustomException(\"账号或密码错误\")\n        account = Account.model_validate(admin)\n    elif account.role == '学生':\n        student = await Student.get_or_none(username=account.username).prefetch_related(\"clazz\")\n        if student is None:\n            raise CustomException(\"账号或密码错误\")\n        if student.password != account.password:\n            raise CustomException(\"账号或密码错误\")\n        account = Account.model_validate(student)\n        account.clazzId = student.clazz.id if student and student.clazz else None\n    else:\n        raise CustomException(\"角色错误\")\n    return Result.success(account)\n```\n\n## 注意 Account 的数据模型\n\n```python\nclass Account(BaseModel):\n    model_config = ConfigDict(from_attributes=True)\n\n    id: int | None = None\n    username: str | None = None\n    password: str | None = None\n    newPassword: str | None = None\n    role: str | None = None\n    name: str | None = None\n    avatar: str | None = None\n    clazzId: int | None = None\n```\n\n## 注册接口\n\n```python\n# 注册\n@api_router.post(\"\u002Fregister\")\nasync def register(account: Account):\n    if account.username is None:\n        raise CustomException(\"账号不能为空\")\n    if account.password is None:\n        raise CustomException(\"密码不能为空\")\n    # 设置默认的name\n    if account.name is None:\n        account.name = account.username\n    student = await Student.get_or_none(username=account.username)\n    if student is not None:\n        raise CustomException(\"账号已存在\")\n    create_data = account.model_dump(exclude_unset=True, exclude={\"id\"})\n    await Student.create(**create_data)\n    return Result.success()\n```\n\n## 关联查询的时候，关联的条件 必须加上 if 判断\n\n![](https:\u002F\u002Fcdn.nlark.com\u002Fyuque\u002F0\u002F2025\u002Fpng\u002F751015\u002F1766563574980-341bf1bf-5e06-4f5d-95b3-8447fb6c07df.png)\n\n## 最终学生的分页查询\n\n```python\n# 分页查询数据\n@router.get('\u002FselectPage')\nasync def select_all(name: str = \"\", clazzName: str = \"\", majorName: str = \"\", pageNum: int = 1, pageSize: int = 10):\n    # name__contains表示根据name进行模糊查询  prefetch_related 关联查询到 major模块的数据\n    query = Student.filter(name__contains=name)\n    if clazzName and clazzName != \"\":\n        query = query.filter(clazz__name__contains=clazzName)\n    if majorName and clazzName != \"\":\n        query = query.filter(clazz__major__name__contains=majorName)\n    query = query.prefetch_related(\"clazz__major\")\n    student_list = await query.offset((pageNum - 1) * pageSize).limit(pageSize)\n    total = await query.count()\n    # student_list 转成字典数据\n    # majorName 怎么返回？？\n    # {id=xxx, name=xxx, no=xxx}\n    student_dict_list = [\n        {\n            **StudentPydantic.model_validate(student).model_dump(),  # id=xxx,no=xxx,name=xxx\n            \"clazzId\": student.clazz.id if student.clazz else None,\n            \"clazzName\": student.clazz.name if student.clazz else None,\n            \"majorName\": student.clazz.major.name if student.clazz and student.clazz.major else None\n        }\n        for student in student_list\n    ]\n    page_info = PageInfo(list=student_dict_list, total=total)\n    return Result.success(page_info)\n\n```\n\n\n\n## 个人信息页面 Person.vue\n\n```vue\n\u003Ctemplate>\n  \u003Cdiv style=\"width: 40%\">\n    \u003Cdiv class=\"card\" style=\"padding: 30px\">\n      \u003Cel-form ref=\"formRef\" :model=\"data.user\" :rules=\"data.rules\" label-width=\"100px\" style=\"padding-right: 50px\">\n        \u003Cdiv style=\"margin: 20px 0; text-align: center\">\n          \u003Cel-upload :show-file-list=\"false\" class=\"avatar-uploader\" :action=\"uploadUrl\" :on-success=\"handleFileUpload\">\n            \u003Cimg v-if=\"data.user.avatar\" :src=\"data.user.avatar\" class=\"avatar\" \u002F>\n            \u003Cel-icon v-else class=\"avatar-uploader-icon\">\u003CPlus \u002F>\u003C\u002Fel-icon>\n          \u003C\u002Fel-upload>\n        \u003C\u002Fdiv>\n        \u003Cel-form-item label=\"账号\" prop=\"username\">\n          \u003Cel-input disabled v-model=\"data.user.username\" autocomplete=\"off\" \u002F>\n        \u003C\u002Fel-form-item>\n        \u003Cel-form-item label=\"名称\" prop=\"name\">\n          \u003Cel-input v-model=\"data.user.name\" autocomplete=\"off\" \u002F>\n        \u003C\u002Fel-form-item>\n         \u003Cel-form-item label=\"所属班级\" prop=\"clazzId\">\n          \u003Cel-select placeholder=\"请选择班级\" v-model=\"data.user.clazzId\">\n            \u003Cel-option v-for=\"item in data.classList\" :key=\"item.id\" :label=\"item.name\" :value=\"item.id\">\u003C\u002Fel-option>\n          \u003C\u002Fel-select>\n        \u003C\u002Fel-form-item>\n        \u003Cdiv style=\"text-align: center\">\n          \u003Cel-button type=\"primary\" @click=\"save\">保存\u003C\u002Fel-button>\n        \u003C\u002Fdiv>\n      \u003C\u002Fel-form>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport {reactive, ref} from \"vue\"\nimport request from \"@\u002Futils\u002Frequest\";\nimport {ElMessage} from \"element-plus\";\n\n\u002F\u002F 文件上传的接口地址\nconst uploadUrl = import.meta.env.VITE_BASE_URL + '\u002Ffiles\u002Fupload'\n\nconst formRef = ref()\nconst data = reactive({\n  user: JSON.parse(localStorage.getItem('system-user') || '{}'),\n  classList: [],\n  rules: {\n    username: [\n      {required: true, message: '请输入账号', trigger: 'blur'}\n    ],\n    name: [\n      {required: true, message: '请输入名称', trigger: 'blur'}\n    ],\n  }\n})\n\n\u002F\u002F 查询班级的信息list\nrequest.get('\u002Fclazz\u002FselectAll').then(res => {\n  data.classList = res.data\n})\n\nconst handleFileUpload = (file) => {\n  data.user.avatar = file.data\n}\n\nconst emit = defineEmits([\"updateUser\"])\n\u002F\u002F 把当前修改的用户信息存储到后台数据库\nconst save = () => {\n  formRef.value.validate(valid => {\n    if (valid) {\n      if (data.user.role === '管理员') {\n        request.put('\u002Fadmin\u002Fupdate', data.user).then(res => {\n          if (res.code === '200') {\n            ElMessage.success('更新成功')\n            \u002F\u002F把更新后的用户信息存储到缓存\n            localStorage.setItem('system-user', JSON.stringify(data.user))\n            emit('updateUser')\n          } else {\n            ElMessage.error(res.msg)\n          }\n        })\n      }\n      if (data.user.role === '学生') {\n        request.put('\u002Fstudent\u002Fupdate', data.user).then(res => {\n          if (res.code === '200') {\n            ElMessage.success('更新成功')\n            \u002F\u002F把更新后的用户信息存储到缓存\n            localStorage.setItem('system-user', JSON.stringify(data.user))\n            emit('updateUser')\n          } else {\n            ElMessage.error(res.msg)\n          }\n        })\n      }\n    }\n  })\n}\n\u003C\u002Fscript>\n\n\u003Cstyle scoped>\n.avatar-uploader .avatar {\n  width: 120px;\n  height: 120px;\n  display: block;\n}\n\u003C\u002Fstyle>\n\n\u003Cstyle>\n.avatar-uploader .el-upload {\n  border: 1px dashed var(--el-border-color);\n  border-radius: 6px;\n  cursor: pointer;\n  position: relative;\n  overflow: hidden;\n  transition: var(--el-transition-duration-fast);\n}\n\n.avatar-uploader .el-upload:hover {\n  border-color: var(--el-color-primary);\n}\n\n.el-icon.avatar-uploader-icon {\n  font-size: 28px;\n  color: #8c939d;\n  width: 120px;\n  height: 120px;\n  text-align: center;\n}\n\u003C\u002Fstyle>\n```\n\n## 修改密码接口\n\n```python\n# 修改密码\n@api_router.put(\"\u002FupdatePassword\")\nasync def update_password(account: Account):\n    if account.role == '管理员':\n        admin = await Admin.get_or_none(id=account.id)\n        if admin is None:\n            raise CustomException(\"未找到用户\")\n        if admin.password != account.password:\n            raise CustomException(\"原密码错误\")\n        if admin.password == account.newPassword:\n            raise CustomException(\"新密码不能原密码跟相同\")\n        await Admin.filter(id=admin.id).update(password=account.newPassword)\n    if account.role == '学生':\n        student = await Student.get_or_none(id=account.id)\n        if student is None:\n            raise CustomException(\"未找到用户\")\n        if student.password != account.password:\n            raise CustomException(\"原密码错误\")\n        if student.password == account.newPassword:\n            raise CustomException(\"新密码不能原密码跟相同\")\n        await Student.filter(id=student.id).update(password=account.newPassword)\n    return Result.success(account)\n```\n\n## Password.vue\n\n```vue\n\u003Ctemplate>\n  \u003Cdiv style=\"width: 40%\">\n    \u003Cdiv class=\"card\" style=\"padding: 30px\">\n      \u003Cel-form ref=\"formRef\" :rules=\"data.rules\" :model=\"data.user\" label-width=\"100px\" style=\"padding-right: 50px\">\n        \u003Cel-form-item label=\"原密码\" prop=\"password\">\n          \u003Cel-input v-model=\"data.user.password\" show-password \u002F>\n        \u003C\u002Fel-form-item>\n        \u003Cel-form-item label=\"新密码\" prop=\"newPassword\">\n          \u003Cel-input v-model=\"data.user.newPassword\" show-password \u002F>\n        \u003C\u002Fel-form-item>\n        \u003Cel-form-item label=\"确认新密码\" prop=\"confirmPassword\">\n          \u003Cel-input v-model=\"data.user.confirmPassword\" show-password \u002F>\n        \u003C\u002Fel-form-item>\n        \u003Cdiv style=\"text-align: center\">\n          \u003Cel-button type=\"primary\" @click=\"save\">保存\u003C\u002Fel-button>\n        \u003C\u002Fdiv>\n      \u003C\u002Fel-form>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport {reactive, ref} from \"vue\"\nimport request from \"@\u002Futils\u002Frequest\";\nimport {ElMessage} from \"element-plus\";\nimport router from \"@\u002Frouter\";\n\nconst formRef = ref()\nconst data = reactive({\n  user: JSON.parse(localStorage.getItem('system-user') || '{}'),\n  rules: {\n    password: [\n      {required: true, message: '请输入原密码', trigger: 'blur'}\n    ],\n    newPassword: [\n      {required: true, message: '请输入原密码', trigger: 'blur'}\n    ],\n    confirmPassword: [\n      {required: true, message: '请输入原密码', trigger: 'blur'}\n    ],\n  }\n})\n\n\u002F\u002F 把当前修改的用户信息存储到后台数据库\nconst save = () => {\n  formRef.value.validate(valid => {\n    if (valid) {\n      if (data.user.password === data.user.newPassword) {\n        ElMessage.error('新密码不能和原密码一致')\n        return\n      }\n      if (data.user.newPassword !== data.user.confirmPassword) {\n        ElMessage.error('确认新密码错误')\n        return\n      }\n      request.put('\u002FupdatePassword', data.user).then(res => {\n        if (res.code === '200') {\n          ElMessage.success('修改密码成功')\n          \u002F\u002F把更新后的用户信息存储到缓存\n          localStorage.setItem('system-user', JSON.stringify(data.user))\n          router.push('\u002Flogin')\n        } else {\n          ElMessage.error(res.msg)\n        }\n      })\n    }\n  })\n}\n\u003C\u002Fscript>\n```\n\n","coding",1,164,2234,"2025-12-24 16:54:18","2026-05-03 22:49:02","基于FastAPI+Vue3的学生信息管理系统","student-information",{"project":18,"items":19},{"id":6,"title":15,"slug":16},[20,28,35,42,49,56,63,70,77,84,85,92,98,105],{"id":21,"uuid":22,"project_id":6,"title":23,"type":9,"status":10,"public_enabled":10,"views":24,"sort":25,"created_at":26,"updated_at":27,"project_title":15,"project_slug":16},1118,"ziKCD5pq","00. 资料和脚手架获取方式",1029,2182,"2026-01-08 17:19:23","2026-05-03 22:50:49",{"id":29,"uuid":30,"project_id":6,"title":31,"type":9,"status":10,"public_enabled":10,"views":32,"sort":33,"created_at":34,"updated_at":14,"project_title":15,"project_slug":16},1119,"bxgr45Kb","01. 学生信息管理系统介绍",461,2187,"2025-12-18 18:35:51",{"id":36,"uuid":37,"project_id":6,"title":38,"type":9,"status":10,"public_enabled":10,"views":39,"sort":40,"created_at":41,"updated_at":14,"project_title":15,"project_slug":16},1120,"jlnSrc9J","02. 导入并运行项目脚手架",509,2188,"2025-12-18 18:36:05",{"id":43,"uuid":44,"project_id":6,"title":45,"type":9,"status":10,"public_enabled":10,"views":46,"sort":47,"created_at":48,"updated_at":14,"project_title":15,"project_slug":16},1124,"OIwLCsr8","03. 开发专业信息增删改查功能（一）",366,2198,"2025-12-19 17:28:47",{"id":50,"uuid":51,"project_id":6,"title":52,"type":9,"status":10,"public_enabled":10,"views":53,"sort":54,"created_at":55,"updated_at":14,"project_title":15,"project_slug":16},1128,"anqokUWU","04. 开发专业信息增删改查功能（二）",241,2211,"2025-12-22 17:24:47",{"id":57,"uuid":58,"project_id":6,"title":59,"type":9,"status":10,"public_enabled":10,"views":60,"sort":61,"created_at":62,"updated_at":14,"project_title":15,"project_slug":16},1129,"8MbeMJwP","05. 开发前端页面管理专业信息",239,2212,"2025-12-22 17:25:07",{"id":64,"uuid":65,"project_id":6,"title":66,"type":9,"status":10,"public_enabled":10,"views":67,"sort":68,"created_at":69,"updated_at":14,"project_title":15,"project_slug":16},1136,"wLyZvGac","06. 开发班级信息管理",207,2225,"2026-01-08 18:03:28",{"id":71,"uuid":72,"project_id":6,"title":73,"type":9,"status":10,"public_enabled":10,"views":74,"sort":75,"created_at":76,"updated_at":14,"project_title":15,"project_slug":16},1137,"PIryZqq8","07. 开发学生信息管理功能",191,2226,"2025-12-23 18:00:42",{"id":78,"uuid":79,"project_id":6,"title":80,"type":9,"status":10,"public_enabled":10,"views":81,"sort":82,"created_at":83,"updated_at":14,"project_title":15,"project_slug":16},1141,"PeUzzy9n","08. 优化学生模块代码",167,2233,"2025-12-24 16:54:01",{"id":4,"uuid":5,"project_id":6,"title":7,"type":9,"status":10,"public_enabled":10,"views":11,"sort":12,"created_at":13,"updated_at":14,"project_title":15,"project_slug":16},{"id":86,"uuid":87,"project_id":6,"title":88,"type":9,"status":10,"public_enabled":10,"views":89,"sort":90,"created_at":91,"updated_at":14,"project_title":15,"project_slug":16},1143,"6GmNfUzm","10. 开发课程信息管理模块",161,2235,"2025-12-24 16:54:34",{"id":93,"uuid":94,"project_id":6,"title":95,"type":9,"status":10,"public_enabled":10,"views":81,"sort":96,"created_at":97,"updated_at":14,"project_title":15,"project_slug":16},1148,"u4OeLaw2","11. 开发学生选课功能",2245,"2025-12-25 17:51:39",{"id":99,"uuid":100,"project_id":6,"title":101,"type":9,"status":10,"public_enabled":10,"views":102,"sort":103,"created_at":104,"updated_at":14,"project_title":15,"project_slug":16},1149,"OSx3Mogl","12. 开发学生成绩模块",163,2246,"2026-01-08 18:02:51",{"id":106,"uuid":107,"project_id":6,"title":108,"type":9,"status":10,"public_enabled":10,"views":109,"sort":110,"created_at":111,"updated_at":14,"project_title":15,"project_slug":16},1155,"mnCD9CqK","13. 开发系统公告功能",157,2258,"2025-12-29 15:53:35"]