
简介基于Python的Django-vue高校实验室管理系统源码与演示视频压缩包适合高校实验室管理员、教学秘书及相关开发学习者使用。项目采用Django后端与Vue.js前端分离架构并用MySQL 5.7以上版本存储数据覆盖实验室信息管理、实验课程编排、设备预约、实验报告提交与在线批改、用户权限控制等核心业务能够直接支撑日常实验室信息化管理。压缩包共602个文件大小约103.69MB其中包含46个py后端源码文件、113个vue前端组件、161个svg及105个jpg图片资源、41个js脚本、15个css样式、2个sql数据库脚本、1个mp4演示视频另有若干批量安装运行bat脚本目录清晰便于二次开发与部署目前已有92人学习下载。借助内置的演示视频、可视化操作界面及管理后台读者可快速理解系统功能与操作流程同时借助源码结构与数据库脚本掌握DjangoVue的完整实现思路是一份兼顾实践演示与工程参考价值的实验室管理系统资源。1. 用 Django-vue 拆高校实验室管理系统先搞清这套源码在管什么高校实验室管理最头疼的不是设备采购而是“借、约、还、修”四个动作全靠微信群和签到表。老师调课撞上学生预约实验员年底盘点时对着 excel 对不上账这套基于 Python 的 Django-vue 高校实验室管理系统就是把上述流程固化成一套前后端分离的应用Django 负责把预约规则、设备台账、人员权限落成 APIvue 负责把日历、时段、审核状态渲染成可点可看的页面。加上源码包和演示视频意味着它不只是论文里的架构图而是一套能运行、能改、能交付的完整工程。适合正在做课程设计或毕设的人也适合刚接手实验室信息化项目的开发人员——前者能直接复用业务模型后者能看清前后端分离项目里最容易被忽略的预约冲突、鉴权和部署问题。2. 先立数据地基Django 模型与实验室业务表怎么设计2.1 为什么选 Django 原生 ORM 而不是 SQLAlchemy做一个实验室管理系统业务量级就是几百个学生、几十间实验室、每天几十条预约离“高并发”还差得很远。这时候选型的第一原则不是性能而是开发效率和管理后台。Django 的 ORM 与 migration 机制深度绑定改完模型跑一条makemigrations就能同步数据库结构省掉了 SQLAlchemy 那种单独维护 Alembic 脚本的步骤。更关键的是 Django 自带 admin 后台实验员不会写 SQL 也能在网页里核对预约记录。我见过不少团队在这个规模上用 FastAPI SQLAlchemy最后都要自己写用户认证和后台反而拖慢交付。这套管理系统的定位是内部工具Django 的“全家桶”属性比微框架更合适。2.2 四个核心模型用户、实验室、设备、预约单2.2.1 用户模型扩展 AbstractUser 还是单独建 Profile实验室系统里有三种角色学生、教师、实验员。Django 自带的User只有is_staff和is_superuser不足以表达这三种身份。常见做法是继承AbstractUser加一个user_type字段而不是单独建 Profile 表——因为这套系统的登录方式只有账号密码不需要为第三方认证预留额外字段。from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): USER_TYPE_CHOICES ( (student, 学生), (teacher, 教师), (admin, 实验员), ) user_type models.CharField(max_length10, choicesUSER_TYPE_CHOICES, defaultstudent) student_no models.CharField(max_length20, blankTrue, nullTrue, verbose_name学号) phone models.CharField(max_length11, blankTrue, nullTrue, verbose_name手机号) class Meta: db_table sys_user verbose_name 用户这里把student_no设为blankTrue, nullTrue因为教师和实验员没有学号不能把学号设成必填。db_table显式指定表名是为了和后续 vue 端联调时对字段名更直观。2.2.2 Lab 模型冗余字段比关联查询更实用实验室表的字段不宜过多但楼栋、房间号、容纳人数、设备清单这几个必须冗余进去。很多人会把设备单独建表然后用外键关联但对于实验室管理系统管理员在列表页最常看的是“这间实验室能坐多少人、有没有投影”冗余字段能让查询少一次 join。class Lab(models.Model): name models.CharField(max_length50, uniqueTrue, verbose_name实验室名称) building models.CharField(max_length30, verbose_name楼栋) room_no models.CharField(max_length10, verbose_name房间号) capacity models.IntegerField(default30, verbose_name容纳人数) has_projector models.BooleanField(defaultTrue, verbose_name是否有投影) equipment models.TextField(blankTrue, verbose_name设备清单) status models.BooleanField(defaultTrue, verbose_name是否开放预约) class Meta: db_table lab_info ordering [building, room_no]equipment用 TextField 存纯文本不要为了省事塞一个 JSON 字段。原因是在 Django admin 里纯文本可以直接编辑而 JSON 字段在后台需要额外写格式化脚本。2.2.3 Reservation 表状态机决定业务边界预约单是整个系统的核心表状态字段至少要覆盖“待审核、已通过、已拒绝、已取消、已完成”五种。很多新手只记录通过和不通过结果教师取消后没有状态可回退导致排课冲突。class Reservation(models.Model): STATUS_CHOICES ( (pending, 待审核), (approved, 已通过), (rejected, 已拒绝), (canceled, 已取消), (finished, 已完成), ) lab models.ForeignKey(Lab, on_deletemodels.CASCADE, related_namereservations) user models.ForeignKey(User, on_deletemodels.CASCADE, related_namereservations) date models.DateField(verbose_name预约日期) start_time models.TimeField(verbose_name开始时间) end_time models.TimeField(verbose_name结束时间) purpose models.CharField(max_length200, verbose_name用途) status models.CharField(max_length10, choicesSTATUS_CHOICES, defaultpending) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table lab_reservation ordering [-created_at]这里related_namereservations让你在 vue 端拼数据时可以直接用user.reservations.all()取某个人的全部预约记录不需要额外构造查询。2.3 迁移与数据字典从模型到表的落地命令模型写完之后需要让 Django 把模型同步到数据库。这里涉及“django 创建 app”之后的标准流程python manage.py makemigrations python manage.py migratemakemigrations只是生成迁移脚本不会改数据库migrate才会真正执行建表。如果你改了模型字段但忘记执行makemigrations启动时会直接报django.db.utils.ProgrammingError这个是新手最容易踩的坑。建议在迁移后立刻去 admin 后台核对字段名确认sys_user、lab_info、lab_reservation三张表都出现在 Django admin 的首页列表里。2.4 与“django 执行查询-删除对象”相关的细节外键级联删除实验员账号时on_deletemodels.CASCADE会连带删除该实验员名下的全部预约记录。这套设计在实验室系统里其实有隐患如果某位老师离职他的历史预约记录作为审计数据应该保留。所以更稳妥的做法是把Reservation.user的外键改为on_deletemodels.SET_NULL, nullTrue这样删除用户后预约单保留只是user字段变空。从“系统可审计”的角度看这是比 CASCADE 更严谨的选择。# 推荐删除用户时保留预约单 user models.ForeignKey( User, on_deletemodels.SET_NULL, nullTrue, related_namereservations, )3. Django REST Framework 把预约时序写成可调用的 API3.1 ViewSet 还是 APIView这套系统应该选哪个前后端分离后后端只需要提供 JSON 接口不再渲染模板。DRF 提供了APIView和ViewSet两种写法。实验室管理系统的接口数量大概在十五个左右登录、用户信息、实验室列表、预约增删改查、审核、设备列表。这个规模下用ModelViewSet最省事它把 list、create、update、delete 全部用默认实现撑起来你只需要关注需要定制的部分。3.2 预约冲突检查序列化器里写还是模型里写预约冲突是这套系统最关键的校验逻辑。常见错误是把冲突检查放在前端 vue 里做然后后端不设防。演示视频里看着没问题一旦两个人同时在浏览器点预约前端检查形同虚设。正确做法是在 DRF 的序列化器里做二次校验并且要考虑四种边界同一间实验室同一天同一时段只能有一个已通过的预约预约开始时间必须早于结束时间预约时间不能是过去时间实验室status为 False 时不允许预约。from rest_framework import serializers from .models import Reservation class ReservationSerializer(serializers.ModelSerializer): class Meta: model Reservation fields [id, lab, date, start_time, end_time, purpose, status] def validate(self, attrs): if attrs[start_time] attrs[end_time]: raise serializers.ValidationError(开始时间必须早于结束时间) if attrs[date] timezone.now().date(): raise serializers.ValidationError(不能预约过去的日期) conflict Reservation.objects.filter( labattrs[lab], dateattrs[date], status__in[pending, approved], ).filter( start_time__ltattrs[end_time], end_time__gtattrs[start_time], ) if conflict.exists(): raise serializers.ValidationError(该时段已被预约或正在审核中) return attrs这段校验的核心是时间区间重叠判断start_time__ltattrs[end_time]表示已有预约的开始时间早于新预约的结束时间end_time__gtattrs[start_time]表示已有预约的结束时间晚于新预约的开始时间。两个条件同时成立时间段就存在交叉。3.3 预约列表接口要支持的多条件过滤vue 端要在一个页面上切换“按实验室”“按日期”“按状态”查看预约单所以预约接口必须支持组合过滤。DRF 自带的django-filter可以直接完成这个需求比手写query_params判断精简很多。pip install django-filter安装后在settings.py里注册INSTALLED_APPS [ # ...其他应用 django_filters, rest_framework, ] REST_FRAMEWORK { DEFAULT_FILTER_BACKENDS: [django_filters.rest_framework.DjangoFilterBackend], }视图集中启用过滤from rest_framework import viewsets from django_filters.rest_framework import DjangoFilterBackend class ReservationViewSet(viewsets.ModelViewSet): queryset Reservation.objects.all() serializer_class ReservationSerializer filter_backends [DjangoFilterBackend] filterset_fields [lab, status, date]配置好之后vue 端只需要传参数curl http://127.0.0.1:8000/api/reservations/?lab3statuspendingdate2025-06-10就会返回lab3且状态为待审核且日期为 2025-06-10 的预约列表。DEFAULT_FILTER_BACKENDS是全局配置让所有 viewset 都能用filterset_fields是本视图允许过滤的字段白名单未列出的字段即使传了参数也不会生效。3.4 权限控制谁能审核谁能取消预约审核的权限模型很简单——学生和教师只能创建和查看自己的预约实验员才能把pending改成approved。DRF 权限类不需要复杂集成自定义一个权限类就够from rest_framework import permissions class IsLabAdmin(permissions.BasePermission): def has_permission(self, request, view): return request.user.is_authenticated and request.user.user_type admin如果把权限管控得更细可以从“操作类型”维度去切分。下面这张表是这套系统常用的权限矩阵操作学生教师实验员创建预约允许允许允许取消本人预约允许允许允许审核预约禁止禁止允许编辑实验室信息禁止禁止允许查看全部预约禁止禁止允许has_permission只控制“能不能访问这个接口”控制“能不能修改这条数据”还需要写has_object_permission否则学生可以带上别人的reservation_id直接发起 DELETE 请求把别人的预约删掉。3.5 用 curl 验证三个关键 API写完接口后不要急着写前端先用 curl 把接口通一遍确认数据格式正确再让 vue 对接。# 获取 token curl -X POST http://127.0.0.1:8000/api/token/ \ -H Content-Type: application/json \ -d {username: student01, password: 123456} # 带上 token 查预约列表 curl http://127.0.0.1:8000/api/reservations/?statusapproved \ -H Authorization: Bearer token # 创建预约 curl -X POST http://127.0.0.1:8000/api/reservations/ \ -H Content-Type: application/json \ -H Authorization: Bearer token \ -d {lab: 1, date: 2025-06-12, start_time: 14:00, end_time: 15:30, purpose: 数据库实验}上面这三条第一条拿 token第二条验证过滤查询第三条验证创建。如果返回 403那就是权限类写错了如果返回 400 且提示时段冲突那就是 validate 校验生效了。用这套顺序排错能区分出问题出在认证层还是业务层。4. Vue 前端对接 Django路由守卫与 Token 刷新的三处细节4.1 用 Vite 创建 vue 项目并安装依赖后端接口跑通之后前端用 Vue3 Vite 是最主流的组合不要再用 Vue CLI。创建一个项目需要三行命令npm create vitelatest lab_frontend -- --template vue cd lab_frontend npm install axios vue-router piniaaxios负责 HTTP 请求vue-router做前端路由pinia做全局状态管理比如把用户信息放在 store 里。如果网络环境下载慢可以把 npm 源切到国内镜像这里不展开。4.2 axios 实例与请求拦截token 过期不能只弹登录框vue 端最容易出 bug 的地方是 token 过期。Django 后端如果用djangorestframework-simplejwt默认 access token 只有 5 分钟有效期vue 端在用户刚打开页面时请求用户信息就会 401。前端不能只做“跳回登录页”这种粗暴处理要封装 axios 拦截器在 401 时自动用 refresh token 换新 token。import axios from axios; const api axios.create({ baseURL: http://127.0.0.1:8000/api, timeout: 5000, }); api.interceptors.request.use((config) { const token localStorage.getItem(access_token); if (token) { config.headers.Authorization Bearer ${token}; } return config; }); api.interceptors.response.use( (response) response, async (error) { const originalRequest error.config; if (error.response?.status 401 !originalRequest._retry) { originalRequest._retry true; const refreshToken localStorage.getItem(refresh_token); if (refreshToken) { const res await axios.post(http://127.0.0.1:8000/api/token/refresh/, { refresh: refreshToken, }); localStorage.setItem(access_token, res.data.access); originalRequest.headers.Authorization Bearer ${res.data.access}; return api(originalRequest); } } localStorage.clear(); window.location.href /login; return Promise.reject(error); }, ); export default api;这段代码的核心逻辑在响应拦截器里originalRequest._retry true防止死循环重试刷新 token 后重新发起一次原本失败的请求。_retry这个自定义属性很关键如果不加token 失效后每个 401 请求都会再去刷新一次 token导致高频接口互相重置 token。4.3 路由守卫哪些页面必须登录才能访问实验室管理系统的页面分成两类登录页是公共页预约列表、我的预约、审核页都需要登录。vue 端用全局前置守卫控制import { createRouter, createWebHistory } from vue-router; const routes [ { path: /login, component: () import(/views/LoginView.vue) }, { path: /labs, component: () import(/views/LabListView.vue), meta: { requiresAuth: true } }, { path: /reservations, component: () import(/views/ReservationView.vue), meta: { requiresAuth: true } }, { path: /admin/audit, component: () import(/views/AuditView.vue), meta: { requiresAuth: true, role: admin } }, ]; router.beforeEach((to, from, next) { const token localStorage.getItem(access_token); if (to.meta.requiresAuth !token) { next(/login); } else { next(); } });meta.role在这里只是先带着真正判断角色要结合后端的权限类和 vue 端在路由守卫里读user_type。注意不要把角色判断写死在前端——前端的隐藏按钮只能算交互优化后端的权限校验才是安全边界。4.4 预约页面的时段渲染预约页面的核心是让用户选择日期和时段后实时显示冲突反馈。这里用一个简单的表格把一天的时段切成 8 格每格 90 分钟渲染时把已预约的时段置灰。const timeSlots [08:00-09:30, 10:00-11:30, 14:00-15:30, 16:00-17:30]; const isConflict (slot) { const [start, end] slot.split(-); return reservations.value.some( (r) r.lab selectedLab.value r.start_time start r.end_time end [pending, approved].includes(r.status), ); };isConflict函数在模板里被循环调用优点是实现直观缺点是如果预约数据量大每次渲染都要遍历一次数组。考虑到实验室系统单日预约量很难超过 20 条这种写法的性能完全足够不必过度设计用计算属性做缓存。4.5 部署环境里被忽视的差异前端和后端在开发环境上是两个进程vue 跑 5173 端口Django 跑 8000 端口。发布到服务器时常见的做法是用 Nginx 托管 vue 的 dist 目录把/api路径反向代理到 Django。这里有个版本层面的坑如果你的服务器是国产化环境比如麒麟python3命令可能不带pip需要先安装 pip 再装依赖sudo dnf install python3-pip python3 -m venv venv source venv/bin/activate pip install -r requirements.txt python manage.py collectstaticcollectstatic这一步很容易漏。Django admin 后台的静态文件在 DEBUGFalse 时不会自动提供必须手动收集到STATIC_ROOT目录否则登录后台时样式全丢。5. 拿到源码包后对照演示视频复查的三张清单5.1 zip 包里应该核对的文件结构拿到“源码-演示视频.zip”之后第一件事不是解压跑代码而是核对文件结构。一个完整的 Django-vue 前后端分离项目解压后至少要看到两份独立的工程和一个说明文件文件/目录作用缺失时的后果backend/manage.pyDjango 后端入口无法启动后端backend/requirements.txt依赖清单环境装不全启动报 ModuleNotFoundErrorfrontend/package.jsonvue 前端依赖声明无法执行 npm installfrontend/vite.config.js前端代理配置前端请求 8000 端口会跨域README.md部署说明数据库密码、密钥等敏感信息未知如果压缩包里只有 backend 没有 frontend那这个包大概率不是完整的前后端分离项目。5.2 按演示视频的顺序走一遍最小启动流程演示视频一般是从“启动后端”开始照着做cd backend python -m venv venv source venv/bin/activate pip install -r requirements.txt python manage.py migrate python manage.py createsuperuser python manage.py runservercreatesuperuser创建的是超级管理员登录 vue 端之后角色是实验员。演示视频里的“大一学生账号”“张老师账号”通常是预置数据需要执行python manage.py loaddata init_data.json才能导入。后端启动后另开一个终端cd frontend npm install npm run dev浏览器打开http://localhost:5173输入管理员账号如果页面能进入预约列表页说明前后端打通了。5.3 三张验证清单数据库、接口、页面对照演示视频逐条勾选这三个层面数据库层Django admin 里能看到三张主业务表预约单里能查到演示视频里出现的几条数据。接口层浏览器访问http://127.0.0.1:8000/api/labs/确认能返回 JSON 数组而不是 HTML 报错。页面层用学生账号创建一个预约再用管理员账号在审核页能看到这条记录并点击通过然后回到学生账号看到状态变成“已通过”整个闭环能在三分钟内完成。如果状态不同步优先查后端返回的 JSON 里status字段值有没有更新而不是先怀疑 vue 端。5.4 一个值得做的升级把冲突检查从 Python 层挪到数据库层序列化器里的冲突检查是应用层锁两个人同时提交时仍有极小概率绕过检查。要让系统更稳可以在 Reservation 表上建一个唯一约束把“实验室 日期 时段”组合设成唯一键。前提是时段不是自由选择的而是固定枚举值。class Meta: constraints [ models.UniqueConstraint( fields[lab, date, start_time, end_time, status], nameunique_reservation_slot, ) ]把唯一约束和序列化器校验叠加使用应用层给出友好的中文提示数据库层做最后的兜底。两行代码的改动能避免并发预约时出现同一时段被审批通过两份的问题。本文还有配套的精品资源点击获取