分享一套锋哥原创的基于Python的高校大学生迎新(新生报到)管理系统(FastAPI+Vue3)

分享一套锋哥原创的基于Python的高校大学生迎新(新生报到)管理系统(FastAPI+Vue3)
大家好我是Java1234_小锋老师分享一套锋哥原创的基于Python的高校大学生迎新(新生报到)管理系统(FastAPIVue3)项目介绍随着高等教育规模持续扩大高校每年迎新报到工作面临新生数量多、业务环节复杂、信息流转不畅等问题。传统以纸质表格和人工核验为主的迎新方式容易出现信息重复录入、报到进度难以及时掌握、宿舍与缴费状态割裂等情况已经难以满足数字化校园建设与精细化管理的现实需求。为此本文设计并实现了一套基于Python的高校大学生迎新新生报到管理系统以信息化手段贯通新生注册、信息维护、报到签到、宿舍分配、学费缴纳与通知公告等关键业务。系统采用前后端分离架构后端基于Python语言与FastAPI框架构建RESTful接口利用SQLAlchemy完成对象关系映射数据持久化采用MySQL数据库库名db_orientation表名统一以t_开头前端基于Vue3与Element Plus实现管理端与学生端双入口通过Axios完成接口调用并结合ECharts实现首页数据统计可视化。系统围绕管理员与新生两类角色展开功能设计管理员可进行组织架构维护、新生管理、报到审核、宿舍与费用管理及通知发布新生可完成注册登录、查看报到进度、宿舍信息、缴费记录与校园公告。本文依次完成需求分析、总体设计、数据库设计、系统实现与测试验证。实践表明该系统界面清晰、流程完整、运行稳定能够有效提升迎新工作效率与数据准确性对同类高校信息化系统的建设具有一定参考价值。本系统已完成主要功能模块开发与联调能够支撑迎新业务从信息准备到结果统计的完整闭环具有较好的实用性与推广参考价值。源码下载链接: https://pan.baidu.com/s/1c4XBDrE3tx7QY45dYoxBnA?pwd1234提取码: 1234系统展示核心代码 复杂查询服务 包含 JOIN 分页查询与首页统计数据 from typing import Any, Optional from sqlalchemy import func, select, text from sqlalchemy.orm import Session from models.checkin import Checkin from models.class_info import ClassInfo from models.department import Department from models.dorm_allocation import DormAllocation from models.dorm_building import DormBuilding from models.dorm_room import DormRoom from models.fee import Fee from models.major import Major from models.student import Student from schemas.base import dump_model from schemas.entity import ( CheckinVO, ClassInfoVO, DormAllocationVO, DormRoomVO, FeeVO, MajorVO, StudentVO, ) from utils.datetime_util import normalize_value from utils.response import page_result def _paginate_rows(rows: list, total: int, page_num: int, page_size: int) - dict: 构造分页结果 return page_result(rows, total, page_num, page_size) def get_student_page( db: Session, page_num: int, page_size: int, keyword: Optional[str] None, dept_id: Optional[int] None, checkin_status: Optional[int] None, ) - dict: 分页查询学生含院系、专业、班级名称 query ( select( Student, Department.dept_name.label(dept_name), Major.major_name.label(major_name), ClassInfo.class_name.label(class_name), ) .outerjoin(Department, Student.dept_id Department.id) .outerjoin(Major, Student.major_id Major.id) .outerjoin(ClassInfo, Student.class_id ClassInfo.id) ) count_query select(func.count()).select_from(Student) if keyword: like f%{keyword}% cond ( Student.name.like(like) | Student.student_no.like(like) | Student.username.like(like) ) query query.where(cond) count_query count_query.where(cond) if dept_id is not None: query query.where(Student.dept_id dept_id) count_query count_query.where(Student.dept_id dept_id) if checkin_status is not None: query query.where(Student.checkin_status checkin_status) count_query count_query.where(Student.checkin_status checkin_status) total db.scalar(count_query) or 0 rows ( db.execute( query.order_by(Student.id.desc()) .offset((page_num - 1) * page_size) .limit(page_size) ) .all() ) records [] for student, dept_name, major_name, class_name in rows: vo StudentVO.model_validate(student) data dump_model(vo) data[deptName] dept_name data[majorName] major_name data[className] class_name data.pop(password, None) records.append(data) return _paginate_rows(records, total, page_num, page_size) def get_student_detail(db: Session, student_id: int) - Optional[dict]: 查询学生详情含院系、专业、班级名称 row db.execute( select( Student, Department.dept_name.label(dept_name), Major.major_name.label(major_name), ClassInfo.class_name.label(class_name), ) .outerjoin(Department, Student.dept_id Department.id) .outerjoin(Major, Student.major_id Major.id) .outerjoin(ClassInfo, Student.class_id ClassInfo.id) .where(Student.id student_id) ).first() if not row: return None student, dept_name, major_name, class_name row data dump_model(StudentVO.model_validate(student)) data[deptName] dept_name data[majorName] major_name data[className] class_name data.pop(password, None) return data def get_major_page( db: Session, page_num: int, page_size: int, keyword: Optional[str] None ) - dict: 分页查询专业含院系名称 query ( select(Major, Department.dept_name.label(dept_name)) .outerjoin(Department, Major.dept_id Department.id) ) count_query select(func.count()).select_from(Major) if keyword: like f%{keyword}% cond Major.major_name.like(like) | Major.major_code.like(like) query query.where(cond) count_query count_query.where(cond) total db.scalar(count_query) or 0 rows ( db.execute( query.order_by(Major.id.desc()) .offset((page_num - 1) * page_size) .limit(page_size) ) .all() ) records [] for major, dept_name in rows: data dump_model(MajorVO.model_validate(major)) data[deptName] dept_name records.append(data) return _paginate_rows(records, total, page_num, page_size) def get_class_page( db: Session, page_num: int, page_size: int, keyword: Optional[str] None ) - dict: 分页查询班级含专业、院系名称 query ( select( ClassInfo, Major.major_name.label(major_name), Department.dept_name.label(dept_name), ) .outerjoin(Major, ClassInfo.major_id Major.id) .outerjoin(Department, Major.dept_id Department.id) ) count_query select(func.count()).select_from(ClassInfo) if keyword: like f%{keyword}% cond ClassInfo.class_name.like(like) | ClassInfo.class_code.like(like) query query.where(cond) count_query count_query.where(cond) total db.scalar(count_query) or 0 rows ( db.execute( query.order_by(ClassInfo.id.desc()) .offset((page_num - 1) * page_size) .limit(page_size) ) .all() ) records [] for cls, major_name, dept_name in rows: data dump_model(ClassInfoVO.model_validate(cls)) data[majorName] major_name data[deptName] dept_name records.append(data) return _paginate_rows(records, total, page_num, page_size) def get_fee_page( db: Session, page_num: int, page_size: int, keyword: Optional[str] None, pay_status: Optional[int] None, student_id: Optional[int] None, ) - dict: 分页查询缴费记录含学生信息 from models.student import Student as Stu query ( select(Fee, Stu.name.label(student_name), Stu.student_no.label(student_no)) .outerjoin(Stu, Fee.student_id Stu.id) ) count_query select(func.count()).select_from(Fee) if keyword: like f%{keyword}% query query.where( (Stu.name.like(like)) | (Stu.student_no.like(like)) | (Fee.fee_item.like(like)) ) count_query count_query.join(Stu, Fee.student_id Stu.id).where( (Stu.name.like(like)) | (Stu.student_no.like(like)) | (Fee.fee_item.like(like)) ) if pay_status is not None: query query.where(Fee.pay_status pay_status) count_query count_query.where(Fee.pay_status pay_status) if student_id is not None: query query.where(Fee.student_id student_id) count_query count_query.where(Fee.student_id student_id) total db.scalar(count_query) or 0 rows ( db.execute( query.order_by(Fee.id.desc()) .offset((page_num - 1) * page_size) .limit(page_size) ) .all() ) records [] for fee, student_name, student_no in rows: data dump_model(FeeVO.model_validate(fee)) data[studentName] student_name data[studentNo] student_no records.append(data) return _paginate_rows(records, total, page_num, page_size) def get_checkin_page( db: Session, page_num: int, page_size: int, keyword: Optional[str] None ) - dict: 分页查询报到记录含学生信息 from models.student import Student as Stu query ( select(Checkin, Stu.name.label(student_name), Stu.student_no.label(student_no)) .outerjoin(Stu, Checkin.student_id Stu.id) ) count_query select(func.count()).select_from(Checkin) if keyword: like f%{keyword}% query query.where((Stu.name.like(like)) | (Stu.student_no.like(like))) count_query count_query.join(Stu, Checkin.student_id Stu.id).where( (Stu.name.like(like)) | (Stu.student_no.like(like)) ) total db.scalar(count_query) or 0 rows ( db.execute( query.order_by(Checkin.id.desc()) .offset((page_num - 1) * page_size) .limit(page_size) ) .all() ) records [] for checkin, student_name, student_no in rows: data dump_model(CheckinVO.model_validate(checkin)) data[studentName] student_name data[studentNo] student_no records.append(data) return _paginate_rows(records, total, page_num, page_size) def get_dorm_room_page( db: Session, page_num: int, page_size: int, keyword: Optional[str] None, building_id: Optional[int] None, ) - dict: 分页查询宿舍房间含楼栋名称 query ( select(DormRoom, DormBuilding.building_name.label(building_name)) .outerjoin(DormBuilding, DormRoom.building_id DormBuilding.id) ) count_query select(func.count()).select_from(DormRoom) if keyword: query query.where(DormRoom.room_no.like(f%{keyword}%)) count_query count_query.where(DormRoom.room_no.like(f%{keyword}%)) if building_id is not None: query query.where(DormRoom.building_id building_id) count_query count_query.where(DormRoom.building_id building_id) total db.scalar(count_query) or 0 rows ( db.execute( query.order_by(DormRoom.id.desc()) .offset((page_num - 1) * page_size) .limit(page_size) ) .all() ) records [] for room, building_name in rows: data dump_model(DormRoomVO.model_validate(room)) data[buildingName] building_name records.append(data) return _paginate_rows(records, total, page_num, page_size) def _allocation_row_to_dict(row) - dict: 将宿舍分配 JOIN 查询结果转为字典 allocation, student_name, student_no, building_id, building_name, room_no row data dump_model(DormAllocationVO.model_validate(allocation)) data[studentName] student_name data[studentNo] student_no data[buildingId] building_id data[buildingName] building_name data[roomNo] room_no return data def get_dorm_allocation_page( db: Session, page_num: int, page_size: int, keyword: Optional[str] None ) - dict: 分页查询宿舍分配含学生、房间、楼栋信息 from models.student import Student as Stu query ( select( DormAllocation, Stu.name.label(student_name), Stu.student_no.label(student_no), DormRoom.building_id.label(building_id), DormBuilding.building_name.label(building_name), DormRoom.room_no.label(room_no), ) .outerjoin(Stu, DormAllocation.student_id Stu.id) .outerjoin(DormRoom, DormAllocation.room_id DormRoom.id) .outerjoin(DormBuilding, DormRoom.building_id DormBuilding.id) ) count_query select(func.count()).select_from(DormAllocation) if keyword: like f%{keyword}% query query.where((Stu.name.like(like)) | (Stu.student_no.like(like))) count_query count_query.join(Stu, DormAllocation.student_id Stu.id).where( (Stu.name.like(like)) | (Stu.student_no.like(like)) ) total db.scalar(count_query) or 0 rows ( db.execute( query.order_by(DormAllocation.id.desc()) .offset((page_num - 1) * page_size) .limit(page_size) ) .all() ) records [_allocation_row_to_dict(row) for row in rows] return _paginate_rows(records, total, page_num, page_size) def get_dorm_allocation_by_student(db: Session, student_id: int) - Optional[dict]: 查询指定学生的宿舍分配 from models.student import Student as Stu row db.execute( select( DormAllocation, Stu.name.label(student_name), Stu.student_no.label(student_no), DormRoom.building_id.label(building_id), DormBuilding.building_name.label(building_name), DormRoom.room_no.label(room_no), ) .outerjoin(Stu, DormAllocation.student_id Stu.id) .outerjoin(DormRoom, DormAllocation.room_id DormRoom.id) .outerjoin(DormBuilding, DormRoom.building_id DormBuilding.id) .where(DormAllocation.student_id student_id) .limit(1) ).first() if not row: return None return _allocation_row_to_dict(row) def get_dashboard_stats(db: Session) - dict[str, Any]: 获取首页统计数据 checkin_stats db.execute( text( SELECT COUNT(*) AS total, SUM(CASE WHEN checkin_status 1 THEN 1 ELSE 0 END) AS checkedIn, SUM(CASE WHEN checkin_status 0 THEN 1 ELSE 0 END) AS notCheckedIn FROM t_student ) ).mappings().first() dept_stats db.execute( text( SELECT d.dept_name AS name, COUNT(s.id) AS value FROM t_department d LEFT JOIN t_student s ON d.id s.dept_id GROUP BY d.id, d.dept_name ORDER BY value DESC ) ).mappings().all() fee_stats db.execute( text( SELECT COUNT(*) AS total, SUM(CASE WHEN pay_status 1 THEN 1 ELSE 0 END) AS paid, SUM(CASE WHEN pay_status 0 THEN 1 ELSE 0 END) AS unpaid FROM t_fee ) ).mappings().first() checkin_trend db.execute( text( SELECT DATE_FORMAT(checkin_time, %Y-%m-%d) AS date, COUNT(*) AS count FROM t_checkin WHERE checkin_time IS NOT NULL GROUP BY DATE_FORMAT(checkin_time, %Y-%m-%d) ORDER BY date ASC ) ).mappings().all() return normalize_value( { checkinStats: dict(checkin_stats) if checkin_stats else {}, deptStats: [dict(row) for row in dept_stats], feeStats: dict(fee_stats) if fee_stats else {}, checkinTrend: [dict(row) for row in checkin_trend], } )template div classpage-container !-- 统计卡片 -- el-row :gutter20 classstat-row el-col :span6 div classstat-card div classstat-label新生总数/div div classstat-value{{ stats.checkinStats?.total || 0 }}/div /div /el-col el-col :span6 div classstat-card green div classstat-label已报到/div div classstat-value{{ stats.checkinStats?.checkedIn || 0 }}/div /div /el-col el-col :span6 div classstat-card orange div classstat-label未报到/div div classstat-value{{ stats.checkinStats?.notCheckedIn || 0 }}/div /div /el-col el-col :span6 div classstat-card blue div classstat-label缴费完成率/div div classstat-value{{ payRate }}%/div /div /el-col /el-row !-- 图表区域 -- el-row :gutter20 stylemargin-top: 20px el-col :span12 div classchart-card h3各院系新生人数/h3 div refdeptChartRef classchart-box/div /div /el-col el-col :span12 div classchart-card h3报到情况统计/h3 div refcheckinChartRef classchart-box/div /div /el-col /el-row el-row :gutter20 stylemargin-top: 20px el-col :span12 div classchart-card h3缴费情况统计/h3 div reffeeChartRef classchart-box/div /div /el-col el-col :span12 div classchart-card h3每日报到趋势/h3 div reftrendChartRef classchart-box/div /div /el-col /el-row /div /template script setup import { ref, computed, onMounted, onUnmounted } from vue import * as echarts from echarts import { getDashboardStats } from /api/index const stats ref({}) const deptChartRef ref() const checkinChartRef ref() const feeChartRef ref() const trendChartRef ref() let charts [] /** 缴费完成率 */ const payRate computed(() { const f stats.value.feeStats if (!f || !f.total || f.total 0) return 0 return Math.round((f.paid / f.total) * 100) }) /** 初始化图表 */ const initCharts () { // 院系柱状图 const deptChart echarts.init(deptChartRef.value) const deptData stats.value.deptStats || [] deptChart.setOption({ tooltip: { trigger: axis }, xAxis: { type: category, data: deptData.map(d d.name), axisLabel: { rotate: 15 } }, yAxis: { type: value, minInterval: 1 }, series: [{ type: bar, data: deptData.map(d d.value), itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: #667eea }, { offset: 1, color: #764ba2 } ])}, barWidth: 40%, borderRadius: [6, 6, 0, 0] }] }) charts.push(deptChart) // 报到饼图 const checkinChart echarts.init(checkinChartRef.value) const cs stats.value.checkinStats || {} checkinChart.setOption({ tooltip: { trigger: item }, legend: { bottom: 0 }, series: [{ type: pie, radius: [40%, 70%], data: [ { name: 已报到, value: cs.checkedIn || 0, itemStyle: { color: #67c23a } }, { name: 未报到, value: cs.notCheckedIn || 0, itemStyle: { color: #f56c6c } } ], label: { formatter: {b}: {c}人 ({d}%) } }] }) charts.push(checkinChart) // 缴费饼图 const feeChart echarts.init(feeChartRef.value) const fs stats.value.feeStats || {} feeChart.setOption({ tooltip: { trigger: item }, legend: { bottom: 0 }, series: [{ type: pie, radius: [40%, 70%], data: [ { name: 已缴费, value: fs.paid || 0, itemStyle: { color: #409eff } }, { name: 未缴费, value: fs.unpaid || 0, itemStyle: { color: #e6a23c } } ], label: { formatter: {b}: {c}项 ({d}%) } }] }) charts.push(feeChart) // 报到趋势折线图 const trendChart echarts.init(trendChartRef.value) const trend stats.value.checkinTrend || [] trendChart.setOption({ tooltip: { trigger: axis }, xAxis: { type: category, data: trend.map(t t.date) }, yAxis: { type: value, minInterval: 1 }, series: [{ type: line, data: trend.map(t t.count), smooth: true, areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: rgba(64,158,255,0.3) }, { offset: 1, color: rgba(64,158,255,0.05) } ])}, itemStyle: { color: #409eff }, lineStyle: { width: 3 } }] }) charts.push(trendChart) } onMounted(async () { const res await getDashboardStats() stats.value res.data initCharts() window.addEventListener(resize, () charts.forEach(c c.resize())) }) onUnmounted(() { charts.forEach(c c.dispose()) charts [] }) /script style scoped .stat-row { margin-bottom: 0; } .chart-card { background: #fff; border-radius: 12px; padding: 20px; box-shadow: 0 2px 12px rgba(0,0,0,0.06); } .chart-card h3 { margin-bottom: 12px; color: #303133; font-size: 16px; border-left: 4px solid #409eff; padding-left: 10px; } .chart-box { height: 320px; } /style