
简介这是一套面向计算机专业本科生的医院管理系统毕业设计完整交付包聚焦医疗信息化场景下的前后端分离开发实践助力学生快速完成课题答辩与系统演示。资源包含799个文件总大小43.27MB涵盖57个核心Python后端逻辑文件、48个Vue前端组件如IndexHeader.vue、update-password.vue等、155个JS交互脚本、162个SVG图标资源、56个JPG/PNG界面素材、2个SQL数据库脚本及2个MP4视频演示文件辅以安装/运行/构建三类BAT批处理脚本显著降低环境部署门槛。已有136人学习下载覆盖管理员、医生、护士三类角色权限功能模块完整——从科室与病床管理、药品与药方调度到内部论坛与系统配置均提供可运行源码、MySQL数据库、配套毕业论文及实操视频。结构清晰、注释规范特别适合DjangoVue全栈入门与毕设快速落地。1. 医院管理系统毕业设计为什么用 Django Vue 做前后端分离比写个单页 Flask 更能体现工程能力很多同学做毕业设计时看到“医院管理系统”就本能想用 PHP Bootstrap 拼个后台页面或者用 Flask 写个带模板的 CRUD。但真正拉开差距的是能否把「业务复杂度」和「技术分层意识」同时落地——比如挂号排班、医生坐诊时段冲突校验、检验报告状态机流转、多角色权限隔离管理员/医生/护士/患者这些不是靠if-else堆出来的而是靠 Django 的 ORM 关系建模、Admin 自动化管理、中间件鉴权配合 Vue 的组件化封装、路由守卫、Axios 请求拦截来协同完成的。这个选型不是为了炫技而是因为 Django 天然支持事务控制比如退号退费必须原子性、内置用户认证体系可直接复用auth.User扩展为医生/患者模型、迁移脚本可版本化python manage.py makemigrations生成 SQL 可审阅Vue 则让前端能独立开发挂号表单、检查单预览、电子病历富文本编辑等高交互模块不依赖后端模板渲染。适合计算机、软件工程、信息管理专业学生——既需要展示数据库设计能力MySQL 表结构需满足第三范式又要体现现代 Web 工程规范API 接口文档、跨域配置、静态资源分离部署。如果你的答辩老师问“为什么不用 Spring Boot”答案不是“我不会 Java”而是“Django 的 Model 层对医疗领域实体建模更直观比如Appointment关联Doctor和Patient时ForeignKey字段自动处理外键约束与级联删除且admin.site.register()三行代码就能交付一个可查可改的排班管理后台”。2. Django 后端从零初始化医院核心模型用 migrations 控制数据库演进2.1 设计符合医疗业务逻辑的 MySQL 表结构医院系统不是博客或商城其数据关系有强业务约束一个医生一天可排多个号段但每个号段只能被一个患者占用检验报告生成后不可修改但状态可从“待采样”→“已检测”→“已审核”→“已发布”流转患者档案需区分门诊号与住院号。因此不能简单套用通用用户表而要基于 Django ORM 显式定义模型关系# models.py from django.db import models from django.contrib.auth.models import User class Department(models.Model): name models.CharField(max_length50, verbose_name科室名称) code models.CharField(max_length10, uniqueTrue, verbose_name科室编码) class Doctor(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE) department models.ForeignKey(Department, on_deletemodels.PROTECT, verbose_name所属科室) title models.CharField(max_length20, verbose_name职称) # 主任医师/主治医师 is_on_duty models.BooleanField(defaultTrue, verbose_name是否在岗) class Patient(models.Model): id_card models.CharField(max_length18, uniqueTrue, verbose_name身份证号) name models.CharField(max_length20, verbose_name姓名) gender models.CharField(max_length2, choices[(M, 男), (F, 女)]) phone models.CharField(max_length11, blankTrue, verbose_name手机号) class Appointment(models.Model): STATUS_CHOICES [ (SCHEDULED, 已预约), (CHECKED_IN, 已签到), (IN_PROGRESS, 诊疗中), (COMPLETED, 已完成), (CANCELLED, 已取消), ] patient models.ForeignKey(Patient, on_deletemodels.PROTECT, verbose_name患者) doctor models.ForeignKey(Doctor, on_deletemodels.PROTECT, verbose_name接诊医生) schedule_date models.DateField(verbose_name预约日期) session models.CharField(max_length10, choices[(AM, 上午), (PM, 下午)], verbose_name时段) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultSCHEDULED) created_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together (doctor, schedule_date, session) # 防止同一医生同日同时段重复挂号提示on_deletemodels.PROTECT是关键——当删除医生时系统会抛出ProtectedError而非静默删掉所有挂号记录这符合医疗数据审计要求unique_together确保数据库层面强制约束避免应用层漏校验。2.2 用 Django migrations 实现可追溯的数据库变更执行python manage.py makemigrations后Django 会生成0001_initial.py迁移文件其中包含CreateModel和AddField操作。此时不要直接migrate先检查生成的 SQLpython manage.py sqlmigrate hospital 0001输出类似CREATE TABLE hospital_department ( id integer AUTO_INCREMENT NOT NULL PRIMARY KEY, name varchar(50) NOT NULL, code varchar(10) NOT NULL UNIQUE ); -- 注意Django 默认使用 InnoDB 引擎支持事务和外键确认无误后执行python manage.py migrate后续新增“检验报告”模型时只需修改models.py并再次makemigrationsDjango 会生成增量迁移文件如0002_labreport.py内容为AddField或AlterField。这种机制让答辩时能清晰展示“第3次迭代增加了检验报告上传功能对应 migration 文件编号 0002SQL 变更仅新增 report_file 字段不影响历史挂号数据”。2.3 构建 RESTful API 接口用 Django REST Framework 统一响应格式安装 DRFpip install djangorestframework注册到settings.pyINSTALLED_APPS [ # ... 其他 app rest_framework, hospital.api, # 新建的 api app ]定义序列化器serializers.py# api/serializers.py from rest_framework import serializers from .models import Patient, Appointment, Doctor class PatientSerializer(serializers.ModelSerializer): class Meta: model Patient fields [id, name, gender, phone, id_card] class AppointmentSerializer(serializers.ModelSerializer): patient_name serializers.ReadOnlyField(sourcepatient.name) # 关联字段只读展示 doctor_name serializers.ReadOnlyField(sourcedoctor.user.username) class Meta: model Appointment fields [id, patient, doctor, schedule_date, session, status, patient_name, doctor_name, created_at]编写视图views.py# api/views.py from rest_framework import generics, status from rest_framework.response import Response from .models import Appointment, Patient, Doctor from .serializers import AppointmentSerializer, PatientSerializer class AppointmentListView(generics.ListCreateAPIView): queryset Appointment.objects.select_related(patient, doctor__user).all() serializer_class AppointmentSerializer def get_queryset(self): # 支持按医生ID过滤/api/appointments/?doctor3 queryset super().get_queryset() doctor_id self.request.query_params.get(doctor) if doctor_id: queryset queryset.filter(doctor_iddoctor_id) return queryset class AppointmentDetailView(generics.RetrieveUpdateDestroyAPIView): queryset Appointment.objects.all() serializer_class AppointmentSerializer lookup_field id # 使用 id 而非 pk避免与主键混淆配置 URLurls.py# api/urls.py from django.urls import path from . import views urlpatterns [ path(appointments/, views.AppointmentListView.as_view(), nameappointment-list), path(appointments/int:id/, views.AppointmentDetailView.as_view(), nameappointment-detail), ]注意select_related在查询时提前 JOIN 关联表避免 N1 查询问题——当列表返回 20 个挂号记录时不会额外发起 20 次 SQL 查医生姓名而是一次 JOIN 完成。这是性能关键点答辩时可演示django-debug-toolbar中的 SQL 查询数对比。3. Vue 前端实现挂号流程的组件化封装与 Token 鉴权闭环3.1 初始化 Vue 3 项目并配置 Axios 请求拦截器使用 Vite 创建项目比 Vue CLI 更轻量npm create vitelatest hospital-frontend -- --template vue cd hospital-frontend npm install安装 Axios 并创建请求实例src/utils/request.jsimport axios from axios // 创建 axios 实例 const request axios.create({ baseURL: http://localhost:8000/api/, // Django 开发服务器地址 timeout: 10000 }) // 请求拦截器自动添加 token request.interceptors.request.use( config { const token localStorage.getItem(access_token) if (token) { config.headers.Authorization Bearer ${token} } return config }, error Promise.reject(error) ) // 响应拦截器统一错误处理 request.interceptors.response.use( response response, error { if (error.response?.status 401) { localStorage.removeItem(access_token) window.location.href /login // 跳转登录页 } return Promise.reject(error) } ) export default request提示baseURL设为http://localhost:8000/api/而非/api/避免生产环境部署时因反向代理路径导致 404localStorage存 token 是毕业设计常见做法实际项目应配合 HttpOnly Cookie CSRF Token。3.2 封装挂号表单组件用 Composition API 管理状态创建src/components/AppointmentForm.vuetemplate div classappointment-form h3在线挂号/h3 form submit.preventhandleSubmit div classform-group label患者姓名/label input v-modelform.patientName typetext required / /div div classform-group label就诊医生/label select v-modelform.doctorId required option value请选择/option option v-ford in doctors :keyd.id :valued.id {{ d.name }} ({{ d.department }}) /option /select /div div classform-group label预约日期/label input v-modelform.date typedate required / /div div classform-group label就诊时段/label select v-modelform.session required option valueAM上午/option option valuePM下午/option /select /div button typesubmit :disabledloading提交挂号/button p v-iferror classerror{{ error }}/p /form /div /template script setup import { ref, onMounted } from vue import request from /utils/request const form ref({ patientName: , doctorId: , date: , session: AM }) const doctors ref([]) const loading ref(false) const error ref() onMounted(async () { try { const res await request.get(doctors/) doctors.value res.data } catch (e) { error.value 加载医生列表失败 } }) const handleSubmit async () { loading.value true error.value try { await request.post(appointments/, { patient: { name: form.value.patientName }, // 后端需支持嵌套创建患者 doctor: form.value.doctorId, schedule_date: form.value.date, session: form.value.session }) alert(挂号成功请按时就诊) form.value { patientName: , doctorId: , date: , session: AM } } catch (e) { error.value e.response?.data?.detail || 挂号失败请重试 } finally { loading.value false } } /script注意v-model绑定form对象属性onMounted中调用request.get(doctors/)获取医生列表handleSubmit发送 POST 请求。关键点在于await request.post(...)的错误捕获——e.response?.data?.detail可显示 Django 后端返回的具体验证错误如“该时段号源已满”而非笼统的“网络错误”。3.3 配置 Vue Router 实现角色路由守卫安装 Vue Routernpm install vue-router4定义路由src/router/index.jsimport { createRouter, createWebHistory } from vue-router const routes [ { path: /, redirect: /login }, { path: /login, component: () import(/views/Login.vue) }, { path: /dashboard, component: () import(/views/Dashboard.vue), meta: { requiresAuth: true }, children: [ { path: appointments, component: () import(/views/AppointmentList.vue) }, { path: patients, component: () import(/views/PatientList.vue) } ] } ] const router createRouter({ history: createWebHistory(), routes }) // 全局前置守卫检查登录状态 router.beforeEach((to, from, next) { const token localStorage.getItem(access_token) if (to.meta.requiresAuth !token) { next(/login) } else { next() } }) export default router在Login.vue中登录成功后存 token// src/views/Login.vue const login async () { try { const res await request.post(auth/login/, { username: form.username, password: form.password }) localStorage.setItem(access_token, res.data.access_token) router.push(/dashboard/appointments) } catch (e) { error.value 用户名或密码错误 } }4. 前后端联调与 MySQL 部署解决跨域、静态资源、数据库连接三大典型问题4.1 Django 开发服务器配置 CORS避免 Vue 请求被浏览器拦截安装django-cors-headerspip install django-cors-headers配置settings.pyINSTALLED_APPS [ # ... 其他 app corsheaders, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, # 必须放在 SecurityMiddleware 之后 django.middleware.security.SecurityMiddleware, # ... 其他 middleware ] # 开发环境允许所有来源生产环境需指定域名 CORS_ALLOW_ALL_ORIGINS True # 或更安全的写法 # CORS_ALLOWED_ORIGINS [ # http://localhost:5173, # Vite 默认端口 # http://127.0.0.1:5173, # ] # 允许携带凭证如 cookies CORS_ALLOW_CREDENTIALS True提示CORS_ALLOW_ALL_ORIGINS True仅用于本地开发答辩演示时可接受若需部署到宝塔面板应改为CORS_ALLOWED_ORIGINS并填入你的域名如https://hospital.yourdomain.com。注意CorsMiddleware位置必须在SecurityMiddleware之后否则X-Content-Type-Options等头可能被覆盖。4.2 Vue 打包后静态资源与 Django 集成方案Vue 构建命令生成dist/目录需让 Django 能提供这些静态文件修改 Vue 的vite.config.js设置base为/static/export default defineConfig({ base: /static/, // ... 其他配置 })构建前端npm run build将dist/重命名为static/放入 Django 项目根目录与manage.py同级配置 Djangosettings.py# settings.py import os from pathlib import Path BASE_DIR Path(__file__).resolve().parent.parent.parent # 指向项目根目录 STATIC_URL /static/ STATICFILES_DIRS [ BASE_DIR / static, # 指向 Vue 构建后的 static 目录 ] STATIC_ROOT BASE_DIR / collected_static # collectstatic 输出目录添加 URL 路由urls.pyfrom django.contrib import admin from django.urls import path, include from django.views.generic import TemplateView from django.conf import settings from django.conf.urls.static import static urlpatterns [ path(admin/, admin.site.urls), path(api/, include(hospital.api.urls)), # Vue 路由兜底所有非 API/ADMIN 请求都返回 index.html path(, TemplateView.as_view(template_nameindex.html)), ] # 开发环境提供静态文件生产环境由 Nginx 处理 if settings.DEBUG: urlpatterns static(settings.STATIC_URL, document_rootsettings.STATIC_ROOT)注意TemplateView.as_view(template_nameindex.html)是关键——它让 Vue Router 的history模式生效用户访问/dashboard/appointments时 Django 不返回 404而是返回index.html由 Vue Router 解析路径。collectstatic命令python manage.py collectstatic在生产部署时将所有静态文件合并到STATIC_ROOT供 Nginx 直接服务。4.3 MySQL 连接配置与常见报错排查Django 连接 MySQL 需安装mysqlclient推荐或PyMySQL纯 Python 实现无需编译# 方案1安装 mysqlclient需系统级依赖 # Ubuntu/Debian: sudo apt-get install python3-dev default-libmysqlclient-dev build-essential pip install mysqlclient # 方案2安装 PyMySQL免编译适合 Windows 或受限环境 pip install PyMySQL配置settings.py数据库DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: hospital_db, USER: hospital_user, PASSWORD: your_secure_password, HOST: 127.0.0.1, # 或 localhost PORT: 3306, OPTIONS: { init_command: SET sql_modeSTRICT_TRANS_TABLES, charset: utf8mb4, }, } }常见报错及解决报错信息原因解决方案django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module未安装mysqlclient或PyMySQL执行pip install mysqlclient或pip install PyMySQL并在__init__.py中添加import pymysql; pymysql.install_as_MySQLdb()Access denied for user hospital_userlocalhostMySQL 用户权限不足登录 MySQL 执行CREATE USER hospital_userlocalhost IDENTIFIED BY password;GRANT ALL PRIVILEGES ON hospital_db.* TO hospital_userlocalhost;FLUSH PRIVILEGES;1366, Incorrect string value: \\xF0\\x9F\\x92\\x94 for column name at row 1字符集不支持 emoji在 MySQL 中执行ALTER DATABASE hospital_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;ALTER TABLE hospital_patient CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;5. 毕业答辩高频问题预判与实操技巧从数据库设计到接口测试的硬核准备5.1 答辩必问如何证明你的 MySQL 表设计满足第三范式不要只说“我画了 E-R 图”要现场打开models.py和migrations/文件指出具体字段设计如何消除冗余第一范式1NF所有字段都是原子值。例如Patient.id_card是 18 位字符串不存储“张三-110101199001011234”这种组合字段。第二范式2NF非主属性完全依赖于主键。Appointment表主键是id自增schedule_date和session都只依赖id不依赖doctor_id或patient_id的部分。第三范式3NF消除传递依赖。Doctor表中department是外键指向Department表而非直接存科室名称字符串——这样修改科室名称时只需更新Department表一行避免Doctor表中多行重复修改。提示答辩时可打开 MySQL Workbench执行SELECT * FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMAhospital_db AND TABLE_NAMEhospital_appointment;展示外键约束证明doctor_id和patient_id确实关联到其他表。5.2 接口测试用 curl 或 Postman 验证 Django API 是否符合 REST 规范不要只演示 Vue 页面要现场用命令行验证后端能力# 1. 创建测试患者POST curl -X POST http://localhost:8000/api/patients/ \ -H Content-Type: application/json \ -d {name:张三,gender:M,phone:13800138000,id_card:110101199001011234} # 2. 查询所有挂号GET curl http://localhost:8000/api/appointments/?doctor1 # 3. 更新挂号状态PATCH curl -X PATCH http://localhost:8000/api/appointments/1/ \ -H Content-Type: application/json \ -d {status:CHECKED_IN}观察响应体是否包含标准字段成功时返回HTTP 200 OK或201 CreatedBody 包含完整对象 JSON错误时返回HTTP 400 Bad RequestBody 包含{detail: ...}格式错误信息未登录时返回HTTP 401 UnauthorizedHeader 包含WWW-Authenticate: Bearer。5.3 性能优化用 Django Debug Toolbar 定位慢查询在settings.py中启用调试工具if DEBUG: INSTALLED_APPS [debug_toolbar] MIDDLEWARE [debug_toolbar.middleware.DebugToolbarMiddleware] INTERNAL_IPS [127.0.0.1]在模板中加载 toolbar!-- templates/base.html -- {% load debug_toolbar %} {% debug_toolbar %}访问页面后右下角出现黄色小图标点击进入SQL 标签页查看本次请求执行的所有 SQL重点关注SELECT语句是否用了JOIN或WHERE索引Views 标签页看各视图函数耗时若AppointmentListView耗时 200ms说明select_related未生效或数据量过大Templates 标签页检查模板渲染次数避免在循环中多次调用{{ appointment.doctor.user.username }}导致重复查询。实操技巧在AppointmentListView.get_queryset()中添加print(queryset.query)可打印最终 SQL确认是否生成了LEFT OUTER JOIN而非 N1 查询。本文还有配套的精品资源点击获取