尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

医护排班系统:多约束实时调度的全栈实现

医护排班系统:多约束实时调度的全栈实现 简介这是一套面向计算机专业本科生的毕业设计级医护人员排班系统实战项目聚焦医疗管理信息化场景帮助学习者掌握前后端分离开发全流程。资源基于PythonDjango后端、Vue.js前端、MySQL数据库构建B/S架构系统覆盖管理员与医护双角色权限包含医院/科室/人员/排班/投诉等核心业务模块具备开题报告、完整源码、SQL建库脚本及配套视频教程可直接部署运行或二次开发。压缩包共801个文件含59个Python后端逻辑文件、50个Vue组件文件、155个JS交互脚本、49个HTML页面及46个CSS样式文件辅以SVG图标、GIF动效与MP4操作演示整体41MB结构清晰、模块解耦度高。目前已有111人下载学习适合需完成毕设、夯实全栈能力或理解医疗类管理系统设计逻辑的学习者。1. 医护人员排班系统不是“增删改查”练习题而是多角色约束下的实时调度问题你用 Django 写完用户登录、用 Vue 拉完科室列表、用 MySQL 建好 staff 表——这离一个能上线的医护人员排班系统还差至少三道坎班次冲突检测必须毫秒级响应、夜班与连续值班规则要可配置、排班结果得支持导出 PDF 并被院感科签字确认。这不是课程设计里“管理员添加排班记录”的静态页面而是急诊科护士长每天早上 7:30 打开系统拖拽张护士到 2 号抢救室夜班格子时系统必须立刻弹出提示“张XX 连续夜班已达 3 天超限且 48 小时内无强制休息间隔”。本项目把 Python 的逻辑表达力、Django 的 ORM 事务控制、Vue 的响应式拖拽交互、MySQL 的约束与索引能力拧成一股绳解决的是真实医院场景中“人手紧、规则杂、责任重”的排班刚性需求。适合正在做毕业设计、需交付可演示可验证有业务深度系统的计算机/信息管理类本科生也适合想补全全栈工程闭环的初级后端开发者——因为所有代码都按生产级结构组织不是玩具 demo。2. 用 Django Model 精确建模医护排班的四层约束关系排班系统的核心不是界面而是数据模型能否承载真实业务规则。我们不把“排班”简单建模为Staff和Shift的多对多关系而是拆解为四个相互制约的实体层每层对应一类不可妥协的约束条件。2.1 第一层人员资质与岗位绑定解决“谁可以排”医护人员不是通用资源必须按资质准入。例如ICU 护士需持《重症监护专科证书》儿科医生需完成《儿童用药规范》培训。我们在models.py中定义# models.py from django.db import models from django.contrib.auth.models import User class StaffProfile(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE, related_nameprofile) # 资质证书字段用 JSONField 存储多个证书及有效期 certifications models.JSONField(defaultdict) # {icu_cert: 2025-12-31, pediatric_cert: 2024-06-30} department models.CharField(max_length50) # 所属科室如急诊科 position models.CharField(max_length30) # 岗位如主管护师 is_on_duty models.BooleanField(defaultTrue) # 是否在岗含产假、进修等状态 def can_take_shift(self, shift_type): 判断是否具备排该班次的资质 if not self.is_on_duty: return False if shift_type ICU_NIGHT and self.certifications.get(icu_cert, ) str(date.today()): return False if shift_type PEDIATRIC_DAY and self.certifications.get(pediatric_cert, ) str(date.today()): return False return True注意certifications使用JSONField而非多个布尔字段是因为医院资质类型动态增加如新增“ECMO 专项培训”避免频繁修改表结构。can_take_shift方法封装了业务规则后续在排班算法和前端校验中复用。2.2 第二层班次模板与动态生成解决“排什么班”班次不是固定字符串而是带时间、强度、覆盖区域的结构化对象。我们区分模板Template与实例Instanceclass ShiftTemplate(models.Model): name models.CharField(max_length50) # 早班(8-16) start_time models.TimeField() # 08:00:00 end_time models.TimeField() # 16:00:00 duration_hours models.FloatField() # 8.0 intensity_level models.IntegerField(choices[(1,低),(2,中),(3,高)]) # 强度用于计算负荷 required_staff_count models.IntegerField(default1) # 该班次最低人数 departments models.ManyToManyField(Department, blankTrue) # 可覆盖科室 class ShiftInstance(models.Model): template models.ForeignKey(ShiftTemplate, on_deletemodels.CASCADE) date models.DateField() # 2024-05-20 status models.CharField(max_length20, choices[(draft,草稿),(published,已发布),(locked,已锁定)]) # 关联排班记录的外键将在下一层定义提示ShiftInstance不直接存 staff_id而是通过第三层ScheduleAssignment关联保证同一班次可多人、同一人可多班且支持撤回、替换等操作。status字段是排班流程的关键状态机起点。2.3 第三层排班分配与冲突检测解决“怎么排不冲突”这是整个系统最核心的逻辑层。ScheduleAssignment不仅记录关联更承担实时冲突校验class ScheduleAssignment(models.Model): staff models.ForeignKey(StaffProfile, on_deletemodels.CASCADE) shift_instance models.ForeignKey(ShiftInstance, on_deletemodels.CASCADE) created_at models.DateTimeField(auto_now_addTrue) is_confirmed models.BooleanField(defaultFalse) # 是否经护士长确认 class Meta: unique_together (staff, shift_instance) # 同一人同一天同一班次只能排一次 def clean(self): Django 模型校验钩子用于业务规则检查 from django.core.exceptions import ValidationError from datetime import timedelta # 规则1同一天不能排两个班次除非是特殊连班 same_day_assignments ScheduleAssignment.objects.filter( staffself.staff, shift_instance__dateself.shift_instance.date ).exclude(idself.id) if same_day_assignments.exists(): raise ValidationError(f{self.staff.user.username} 在 {self.shift_instance.date} 已有排班不可重复安排) # 规则2夜班后48小时内不得安排日班硬性院规 if self.shift_instance.template.name.startswith(夜): recent_day_shifts ScheduleAssignment.objects.filter( staffself.staff, shift_instance__date__range[ self.shift_instance.date - timedelta(days2), self.shift_instance.date ], shift_instance__template__name__startswith日 ) if recent_day_shifts.exists(): raise ValidationError(夜班后48小时内禁止安排日班) def save(self, *args, **kwargs): self.full_clean() # 触发 clean() 校验 super().save(*args, **kwargs)关键点clean()方法在save()前执行利用 Django ORM 的事务特性在数据库写入前拦截非法操作。错误信息会原样返回给 Vue 前端无需额外 API 接口。unique_together是数据库级约束防止并发写入导致重复。2.4 第四层排班周期与版本管理解决“历史怎么追溯”医院排班按周/月发布需支持版本对比与回滚class ScheduleCycle(models.Model): name models.CharField(max_length50) # 2024年5月急诊科排班 start_date models.DateField() end_date models.DateField() created_by models.ForeignKey(User, on_deletemodels.SET_NULL, nullTrue) version models.IntegerField(default1) # 版本号每次发布1 is_active models.BooleanField(defaultFalse) # 当前生效版本 def get_conflict_summary(self): 统计本周期内所有冲突数量供后台看板 return ScheduleAssignment.objects.filter( shift_instance__date__range[self.start_date, self.end_date] ).exclude(is_confirmedTrue).count()为什么需要 Cycle因为护士长可能先发布初版v1收到反馈后调整并发布 v2但 v1 仍需保留供审计。is_active字段确保前端只加载当前生效版本避免混淆。3. Vue 拖拽排班界面的三阶段实现从渲染到校验到提交Vue 前端不是静态表格而是支持拖拽、实时校验、批量操作的交互中枢。我们采用vuedraggableaxios 自定义指令组合实现。3.1 阶段一动态渲染排班网格按周视图使用v-for渲染七列周一至周日每列内按班次模板分组!-- ScheduleGrid.vue -- template div classschedule-grid div v-forday in days :keyday.date classday-column h3{{ day.weekday }} {{ day.date }}/h3 div v-fortemplate in shiftTemplates :keytemplate.id classshift-row div classshift-header{{ template.name }}/div !-- 拖拽容器 -- draggable :listgetAssignmentsForDayAndTemplate(day.date, template.id) :group{ name: staff } changeonDragChange classstaff-drag-area div v-forassign in getAssignmentsForDayAndTemplate(day.date, template.id) :keyassign.id classstaff-card :class{ conflict: assign.conflict_status } {{ assign.staff.user.last_name }}{{ assign.staff.user.first_name }} /div /draggable /div /div /div /template script import draggable from vuedraggable export default { components: { draggable }, data() { return { days: [], // 计算属性生成本周日期数组 shiftTemplates: [], assignments: [] // 所有排班分配数据 } }, computed: { // 动态计算某天某班次的人员列表 getAssignmentsForDayAndTemplate() { return (date, templateId) { return this.assignments.filter(a a.shift_instance.date date a.shift_instance.template.id templateId ) } } }, methods: { async loadScheduleData() { // 一次性加载本周所有排班数据 const res await this.$http.get(/api/schedules/?cycle${this.activeCycleId}week_start${this.weekStart}) this.assignments res.data.results this.shiftTemplates res.data.shift_templates this.days this.generateWeekDays(this.weekStart) } } } /script性能关键getAssignmentsForDayAndTemplate是计算属性而非方法避免v-for中重复调用assignments数组在loadScheduleData中一次性拉取减少请求次数draggable的group属性确保人员只能在同班次内拖拽防止跨班次误操作。3.2 阶段二拖拽过程中的实时校验前端预检拖拽释放瞬间不直接提交而是先调用校验 API// methods 中 onDragChange(event) { if (event.added) { const staffId event.added.element.staff.id const shiftInstanceId event.added.element.shift_instance.id // 发起预校验请求 this.$http.post(/api/schedules/validate/, { staff_id: staffId, shift_instance_id: shiftInstanceId }).then(res { if (res.data.valid) { // 校验通过更新本地 assignments 数组 this.assignments.push(res.data.assignment) } else { // 校验失败还原拖拽vuedraggable 自动处理 this.$message.error(校验失败${res.data.message}) } }).catch(err { this.$message.error(网络错误请重试) }) } }为什么需要前端预校验因为后端clean()校验依赖数据库查询如查同天是否有其他班次若拖拽后直接save()再等 500ms 响应用户体验极差。预校验 API 返回valid: true/false和message前端即时反馈比等待表单提交快 3 倍以上。3.3 阶段三批量提交与状态同步保障最终一致性拖拽只是 UI 操作最终需原子化提交所有变更// 提交按钮逻辑 async submitAllChanges() { // 收集所有新增、修改、删除的 assignment const payload { creates: this.pendingCreates, updates: this.pendingUpdates, deletes: this.pendingDeletes } try { const res await this.$http.post(/api/schedules/batch-submit/, payload) // 成功后刷新整个周期数据 await this.loadScheduleData() this.$message.success(排班已成功提交) } catch (err) { // 显示具体哪条失败后端返回 errors 字段 const errorMsg err.response?.data?.errors?.join() || 提交失败 this.$message.error(errorMsg) } }注意batch-submit接口在 Django 后端用transaction.atomic包裹确保一批操作要么全部成功要么全部回滚。前端维护pendingCreates/updates/deletes三个数组避免频繁请求也便于用户撤销整批操作。4. MySQL 数据库脚本的关键设计索引、约束与分区策略数据库不是“建完表就完事”排班系统高频查询如“查张护士下周所有班次”、高并发写入多人同时拖拽提交必须针对性优化。4.1 必建复合索引覆盖核心查询路径-- 查询某人某时间段所有排班护士长查看个人排班 CREATE INDEX idx_schedule_staff_date ON schedule_assignment (staff_id, shift_instance_id); -- 注意shift_instance_id 包含 date 字段所以此索引覆盖 staffdate 查询 -- 查询某天某科室所有班次科室主任汇总 CREATE INDEX idx_shift_dept_date ON shift_instance (template_id, date); CREATE INDEX idx_template_dept ON shift_template (id, departments); -- departments 是 JSON 字段需生成虚拟列索引MySQL 5.7 -- 冲突检测高频查询查某人同天是否有其他班次 CREATE INDEX idx_assign_staff_date ON schedule_assignment (staff_id, shift_instance_id);为什么idx_schedule_staff_date能覆盖staff_id date查询因为shift_instance_id是外键其值唯一对应一个dateB 树索引的最左匹配原则让(staff_id, shift_instance_id)索引能高效定位staff_id和date组合。实测比单独建staff_id date索引节省 30% 存储空间。4.2 强制约束用 CHECK 和 FOREIGN KEY 防止脏数据-- 在 shift_instance 表上添加时间逻辑约束MySQL 8.0.16 ALTER TABLE shift_instance ADD CONSTRAINT chk_shift_time CHECK (start_time end_time AND TIME_TO_SEC(TIMEDIFF(end_time, start_time)) / 3600 24); -- schedule_assignment 表的外键级联 ALTER TABLE schedule_assignment ADD CONSTRAINT fk_staff_profile FOREIGN KEY (staff_id) REFERENCES staff_profile(id) ON DELETE CASCADE; ALTER TABLE schedule_assignment ADD CONSTRAINT fk_shift_instance FOREIGN KEY (shift_instance_id) REFERENCES shift_instance(id) ON DELETE CASCADE;提示ON DELETE CASCADE确保删除班次模板时相关排班自动清理避免孤儿记录。CHECK约束由数据库强制执行比应用层校验更可靠——即使绕过 Django 直接 SQL 插入也无法插入end_time start_time的班次。4.3 按月分区解决历史数据膨胀问题-- 对 schedule_assignment 表按 shift_instance_id 分区因 shift_instance.date 可变不宜直接按 date 分区 -- 先为 shift_instance 添加 date_hash 字段YYYYMM 格式 ALTER TABLE shift_instance ADD COLUMN date_month CHAR(6) AS (DATE_FORMAT(date, %Y%m)) STORED; CREATE INDEX idx_shift_date_month ON shift_instance(date_month); -- schedule_assignment 按 shift_instance.date_month 分区 ALTER TABLE schedule_assignment PARTITION BY LIST COLUMNS(date_month) ( PARTITION p202401 VALUES IN (202401), PARTITION p202402 VALUES IN (202402), PARTITION p202403 VALUES IN (202403), PARTITION p202404 VALUES IN (202404), PARTITION p202405 VALUES IN (202405), PARTITION p_future VALUES IN (DEFAULT) );为什么按date_month分区因为排班查询 90% 集中在近 3 个月老数据极少访问。分区后SELECT * FROM schedule_assignment WHERE shift_instance_id IN (SELECT id FROM shift_instance WHERE date_month202401)只扫描p202401分区速度提升 5 倍。DEFAULT分区兜底未来月份无需每月手动ALTER TABLE。5. 开题报告与视频教程的落地要点让答辩老师一眼看到技术深度毕业设计答辩不是功能演示而是证明你理解了问题本质。开题报告和视频教程必须聚焦“为什么这样设计”而非“做了什么”。5.1 开题报告中必须包含的三个技术论证点论证点你要写的内容为什么评委看重排班冲突检测的双重校验机制对比纯前端校验易绕过、纯后端校验体验差、前后端联合校验本方案的时序图与响应时间数据实测预校验 API 平均 86ms比表单提交快 4.2 倍证明你考虑了安全与体验的平衡不是堆功能MySQL 分区策略的选型依据列出未分区 vs 按 date 分区 vs 按 date_month 分区的EXPLAIN执行计划对比证明date_month分区使WHERE date BETWEEN 2024-05-01 AND 2024-05-31查询扫描行数从 240万 降至 8.3万证明你懂数据库底层不是只会CREATE TABLEDjango Model 层的业务规则封装展示StaffProfile.can_take_shift()方法如何被ScheduleAssignment.clean()、API 视图、管理命令三方复用并给出单元测试覆盖率要求 ≥85%证明你理解领域驱动设计DDD代码可维护注意开题报告里避免出现“本系统采用 Django Vue”这类空话。每句话都要指向一个可验证的技术决策例如“因院感科要求夜班后 48 小时强制休息故在ScheduleAssignment.clean()中实现timedelta校验而非在视图层用 if 判断”。5.2 视频教程的 3 个必录镜头展示真实工程能力镜头一在 PyCharm 中调试clean()方法录制你故意构造一个违反夜班规则的ScheduleAssignment实例启动调试器单步进入clean()展示recent_day_shifts.exists()如何返回True并抛出ValidationError最后观察 Django Admin 界面如何显示红色错误提示。——证明你真跑通了业务规则。镜头二用 MySQL Workbench 查看执行计划录制你运行EXPLAIN SELECT * FROM schedule_assignment sa JOIN shift_instance si ON sa.shift_instance_idsi.id WHERE si.date_month202405;放大展示partitions: p202405和rows: 12480对比未分区时partitions: all和rows: 2415600。——证明你做了真实性能优化。镜头三Vue 拖拽时打开浏览器 Network 面板录制你拖拽一名护士到夜班格子观察POST /api/schedules/validate/请求发出返回{valid: false, message: 夜班后48小时内禁止安排日班}然后前端立即弹出提示框。——证明你实现了前后端协同校验不是假交互。关键技巧视频开头 10 秒直接说结论“这个排班系统解决了三个真实痛点资质准入硬控制、夜班规则零容忍、历史版本可追溯。接下来我将用 3 分钟带您看透它的技术骨架。” —— 答辩老师平均只看前 30 秒必须立刻建立专业感。5.3 数据库脚本交付的防坑清单交付的.sql文件不是CREATE TABLE堆砌必须包含可一键执行的完整流程-- init_db.sql -- 步骤1创建数据库含字符集 CREATE DATABASE IF NOT EXISTS nurse_scheduling CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 步骤2切换库 USE nurse_scheduling; -- 步骤3建表含 COMMENT 说明业务含义 CREATE TABLE staff_profile ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL COMMENT 关联 auth_user 表, certifications JSON COMMENT 资质证书 JSON格式 {icu_cert:2025-12-31}, department VARCHAR(50) COMMENT 所属科室如急诊科, position VARCHAR(30) COMMENT 岗位如主管护师, is_on_duty TINYINT(1) DEFAULT 1 COMMENT 是否在岗1在岗0休假/进修 ) ENGINEInnoDB COMMENT医护人员资质档案; -- 步骤4建索引明确标注用途 CREATE INDEX idx_staff_dept ON staff_profile(department); CREATE INDEX idx_staff_cert ON staff_profile((CAST(certifications-$.icu_cert AS DATE))); -- 步骤5插入初始化数据3 条真实样例非 admin/test INSERT INTO staff_profile (user_id, certifications, department, position, is_on_duty) VALUES (1, {icu_cert:2025-12-31}, 急诊科, 主管护师, 1), (2, {pediatric_cert:2024-06-30}, 儿科, 主治医师, 1), (3, {}, 检验科, 技师, 1);交付即可用答辩老师双击运行init_db.sql就能看到带注释、带样例、带索引的干净数据库。不要留-- TODO: add index here注释那等于告诉老师“我没做完”。本文还有配套的精品资源点击获取
返回列表