分享一套锋哥原创的基于Python的微信小程序实验室预约管理系统(FastAPI+Vue3)

📅 2026/7/8 2:46:24
分享一套锋哥原创的基于Python的微信小程序实验室预约管理系统(FastAPI+Vue3)
大家好我是Java1234_小锋老师分享一套锋哥原创的基于Python的微信小程序实验室预约管理系统(FastAPIVue3)项目介绍随着高校教学与科研规模的不断扩大实验室作为实践教学的重要场所其使用需求日益增长。传统的实验室预约方式多依赖线下登记、电话预约或人工排班普遍存在信息不透明、预约效率低、资源冲突频繁以及数据难以统计等问题已难以满足师生便捷、高效使用实验室的实际需要。因此设计并实现一套便捷、规范、高效的实验室预约管理系统具有重要的现实意义。本文以“微信小程序实验室预约管理系统”为研究对象采用前后端分离的架构思想进行设计与实现。系统面向学生用户的前端采用微信小程序开发用户无需下载安装即可随时随地完成实验室查询、在线预约、扫码签到与评价反馈等操作面向管理员的后台管理端采用 Vue3 结合 Element Plus 组件库开发实现用户管理、实验室管理、设备管理、预约审核、签到统计以及公告管理等功能。后端服务采用 Python 语言基于 FastAPI 框架构建 RESTful API使用 Pydantic 完成数据校验通过 JWT 实现登录鉴权并借助 SQLAlchemy ORM 操作 MySQL 数据库保证了系统的高性能与数据一致性。论文按照软件工程的思想系统地开展了需求分析、系统设计、编码实现与系统测试等工作。在需求分析阶段完成了可行性分析与功能需求分析在系统设计阶段完成了总体架构设计、功能模块设计以及以 E-R 图为核心的数据库设计在实现阶段给出了各核心功能模块的关键代码最后通过功能测试验证了系统的正确性与可用性。测试结果表明本系统运行稳定、操作便捷、功能完善能够有效提升实验室预约管理的信息化与智能化水平。源码下载链接: https://pan.baidu.com/s/1fHVvDbJIUTwrgre44PHfmQ?pwd1234提取码: 1234系统展示核心代码 首页轮播图管理路由模块 from fastapi import APIRouter, Depends, Query from fastapi.responses import JSONResponse from sqlalchemy.orm import Session from database import get_db from dependencies import CurrentUser, require_admin from models.banner import Banner from schemas.common import BannerSaveRequest from utils.datetime_util import format_datetime from utils.pagination import paginate from utils.response import fail, page_result, success router APIRouter(prefix/banner, tags[轮播图管理]) router.get(/page) def banner_page( page: int Query(1), size: int Query(10), db: Session Depends(get_db), _user: CurrentUser Depends(require_admin), ): 轮播图分页列表 Args: page: 页码 size: 每页条数 db: 数据库会话 Returns: JSONResponse: 分页数据 q db.query(Banner).order_by(Banner.sort.asc(), Banner.id.desc()) records, total paginate(q, page, size) data [ { id: r.id, image: r.image, bgStyle: r.bg_style, linkUrl: r.link_url, sort: r.sort, status: r.status, createTime: format_datetime(r.create_time), } for r in records ] return JSONResponse(contentsuccess(page_result(data, total))) router.post() def banner_add(body: BannerSaveRequest, db: Session Depends(get_db), _user: CurrentUser Depends(require_admin)): 新增轮播图 Args: body: 轮播图信息 db: 数据库会话 Returns: JSONResponse: 操作结果 banner Banner( imagebody.image, bg_stylebody.bgStyle, link_urlbody.linkUrl, sortbody.sort, statusbody.status, ) db.add(banner) db.commit() return JSONResponse(contentsuccess(None, 添加成功)) router.put() def banner_update(body: BannerSaveRequest, db: Session Depends(get_db), _user: CurrentUser Depends(require_admin)): 修改轮播图 Args: body: 轮播图信息 db: 数据库会话 Returns: JSONResponse: 操作结果 banner db.query(Banner).filter(Banner.id body.id).first() if not banner: return JSONResponse(contentfail(轮播图不存在, 404)) banner.image body.image banner.bg_style body.bgStyle banner.link_url body.linkUrl banner.sort body.sort banner.status body.status db.commit() return JSONResponse(contentsuccess(None, 修改成功)) router.delete(/{id}) def banner_delete(id: int, db: Session Depends(get_db), _user: CurrentUser Depends(require_admin)): 删除轮播图 Args: id: 轮播图 ID db: 数据库会话 Returns: JSONResponse: 操作结果 banner db.query(Banner).filter(Banner.id id).first() if not banner: return JSONResponse(contentfail(轮播图不存在, 404)) db.delete(banner) db.commit() return JSONResponse(contentsuccess(None, 删除成功))template div classpage-card el-form inline classadmin-table-toolbar el-button typeprimary clickload刷新/el-button el-button typesuccess clickopen()新增轮播/el-button /el-form el-table :datarows v-loadingloading classmt border stripe stylewidth: 100% el-table-column propid width64 labelID / el-table-column label预览 min-width160 template #default{ row } div v-ifrow.image classpreview-img-wrap el-image :srcresolveImg(row.image) fitcover classpreview-img / /div div v-else classpreview-bg :style{ background: row.bgStyle || #6366f1 } / /template /el-table-column el-table-column propsort width72 label排序 / el-table-column label状态 width88 template #default{ row } el-tag :typerow.status 1 ? success : info{{ row.status 1 ? 启用 : 禁用 }}/el-tag /template /el-table-column el-table-column label创建时间 width190 template #default{ row }{{ formatDateTimeDisplay(row.createTime) }}/template /el-table-column el-table-column width150 label操作 template #default{ row } el-button link clickopen(row)编辑/el-button el-button link typedanger clickremove(row)删除/el-button /template /el-table-column /el-table el-pagination classmt v-model:current-pageq.page :totaltotal :page-sizeq.size layouttotal, prev, pager, next current-changeload / el-dialog v-modelvis :titlef.id ? 编辑轮播 : 新增轮播 width560px destroy-on-close el-form :modelf label-width88px el-form-item label轮播图片 el-input v-modelf.image placeholder可选上传后自动填写留空则使用渐变背景 / el-upload :show-file-listfalse :http-requestupImg acceptimage/* classup el-button sizesmall本地上传/el-button /el-upload /el-form-item el-form-item label渐变背景 el-input v-modelf.bgStyle placeholder无图时生效如 linear-gradient(90deg,#6366f1,#8b5cf6) / /el-form-item el-form-item label跳转链接el-input v-modelf.linkUrl placeholder可选小程序页面路径或外链 //el-form-item el-form-item label排序el-input-number v-modelf.sort :min0 :max9999 //el-form-item el-form-item label状态el-switch v-modelf.status :active-value1 :inactive-value0 //el-form-item /el-form template #footer el-button clickvis false取消/el-button el-button typeprimary clickok确定/el-button /template /el-dialog /div /template script setup import { onMounted, reactive, ref } from vue import { adminApi } from /api/admin import { ElMessage, ElMessageBox } from element-plus import { formatDateTimeDisplay } from /utils/datetimeDisplay const q reactive({ page: 1, size: 10 }) const rows ref([]) const total ref(0) const loading ref(false) const vis ref(false) const f reactive({ sort: 0, status: 1 }) function resolveImg(path) { if (!path) return if (path.startsWith(http://) || path.startsWith(https://)) return path if (path.startsWith(/)) return path return /api/uploads/ path } async function load() { loading.value true try { const r await adminApi.bannerPage({ page: q.page, size: q.size }) rows.value r.data.records total.value r.data.total } finally { loading.value false } } function open(row) { if (row) { Object.assign(f, { ...row, status: row.status ? 1 : 0 }) } else { Object.assign(f, { id: null, image: , bgStyle: linear-gradient(90deg,#6366f1,#8b5cf6), linkUrl: , sort: 0, status: 1 }) } vis.value true } async function upImg({ file }) { const form new FormData() form.append(file, file) const r await adminApi.upload(form) f.image r.data.url ElMessage.success(上传成功) } async function ok() { if (!f.image?.trim() !f.bgStyle?.trim()) { ElMessage.warning(请上传轮播图片或填写渐变背景) return } if (f.id) await adminApi.bannerUp(f) else await adminApi.bannerAdd(f) vis.value false ElMessage.success(已保存) load() } function remove(row) { ElMessageBox.confirm(确认删除该轮播, { type: warning }) .then(() adminApi.bannerDel(row.id)) .then(() { load() ElMessage.success(已删除) }) .catch(() {}) } onMounted(load) /script style scoped .mt { margin-top: 12px; justify-content: flex-end; } .up { margin-top: 8px; } .preview-img-wrap, .preview-bg { width: 160px; height: 72px; border-radius: 6px; overflow: hidden; } .preview-img { width: 160px; height: 72px; } /style!-- 实验室列表 -- view classcontainer scroll-view scroll-x classcat-bar view classcat-item {{categoryId 0 ? active : }} bindtapselectCat data-id0全部/view view classcat-item {{categoryId item.id ? active : }} wx:for{{categories}} wx:keyid bindtapselectCat data-id{{item.id}} {{item.name}}/view /scroll-view view classlab-list view classlab-row card wx:for{{labs}} wx:keyid bindtapgoDetail data-id{{item.id}} image classthumb src{{item.imageUrl}} modeaspectFill / view classinfo text classname{{item.name}}/text text classloc{{item.location}}/text view classmeta text classtag {{item.statusClass}}{{item.statusText}}/text text classcap容量 {{item.capacity}} 人/text /view /view /view /view view classempty-tip wx:if{{!loading !labs.length}}暂无实验室/view view classload-more wx:if{{hasMore !loading}} button sizemini bindtaploadMore加载更多/button /view view classempty-tip wx:if{{loading}}加载中.../view /view