Django+Vue医院管理系统毕业设计实战指南
2026/9/12 3:58:50 网站建设 项目流程

简介:这是一套面向计算机专业本科生的医院管理系统毕业设计完整交付包,聚焦医疗信息化场景下的前后端分离开发实践,助力学生快速完成课题答辩与系统演示。资源包含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数据库、配套毕业论文及实操视频。结构清晰、注释规范,特别适合Django+Vue全栈入门与毕设快速落地。

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关联DoctorPatient时,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_length=50, verbose_name="科室名称") code = models.CharField(max_length=10, unique=True, verbose_name="科室编码") class Doctor(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) department = models.ForeignKey(Department, on_delete=models.PROTECT, verbose_name="所属科室") title = models.CharField(max_length=20, verbose_name="职称") # 主任医师/主治医师 is_on_duty = models.BooleanField(default=True, verbose_name="是否在岗") class Patient(models.Model): id_card = models.CharField(max_length=18, unique=True, verbose_name="身份证号") name = models.CharField(max_length=20, verbose_name="姓名") gender = models.CharField(max_length=2, choices=[('M', '男'), ('F', '女')]) phone = models.CharField(max_length=11, blank=True, verbose_name="手机号") class Appointment(models.Model): STATUS_CHOICES = [ ('SCHEDULED', '已预约'), ('CHECKED_IN', '已签到'), ('IN_PROGRESS', '诊疗中'), ('COMPLETED', '已完成'), ('CANCELLED', '已取消'), ] patient = models.ForeignKey(Patient, on_delete=models.PROTECT, verbose_name="患者") doctor = models.ForeignKey(Doctor, on_delete=models.PROTECT, verbose_name="接诊医生") schedule_date = models.DateField(verbose_name="预约日期") session = models.CharField(max_length=10, choices=[('AM', '上午'), ('PM', '下午')], verbose_name="时段") status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='SCHEDULED') created_at = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ('doctor', 'schedule_date', 'session') # 防止同一医生同日同时段重复挂号

提示:on_delete=models.PROTECT是关键——当删除医生时,系统会抛出ProtectedError而非静默删掉所有挂号记录,这符合医疗数据审计要求;unique_together确保数据库层面强制约束,避免应用层漏校验。

2.2 用 Django migrations 实现可追溯的数据库变更

执行python manage.py makemigrations后,Django 会生成0001_initial.py迁移文件,其中包含CreateModelAddField操作。此时不要直接migrate,先检查生成的 SQL:

python 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并再次makemigrations,Django 会生成增量迁移文件(如0002_labreport.py),内容为AddFieldAlterField。这种机制让答辩时能清晰展示:“第3次迭代增加了检验报告上传功能,对应 migration 文件编号 0002,SQL 变更仅新增 report_file 字段,不影响历史挂号数据”。

2.3 构建 RESTful API 接口,用 Django REST Framework 统一响应格式

安装 DRF:

pip install djangorestframework

注册到settings.py

INSTALLED_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(source='patient.name') # 关联字段只读展示 doctor_name = serializers.ReadOnlyField(source='doctor.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/?doctor=3 queryset = super().get_queryset() doctor_id = self.request.query_params.get('doctor') if doctor_id: queryset = queryset.filter(doctor_id=doctor_id) return queryset class AppointmentDetailView(generics.RetrieveUpdateDestroyAPIView): queryset = Appointment.objects.all() serializer_class = AppointmentSerializer lookup_field = 'id' # 使用 id 而非 pk,避免与主键混淆

配置 URL(urls.py):

# api/urls.py from django.urls import path from . import views urlpatterns = [ path('appointments/', views.AppointmentListView.as_view(), name='appointment-list'), path('appointments/<int:id>/', views.AppointmentDetailView.as_view(), name='appointment-detail'), ]

注意:select_related在查询时提前 JOIN 关联表,避免 N+1 查询问题——当列表返回 20 个挂号记录时,不会额外发起 20 次 SQL 查医生姓名,而是一次 JOIN 完成。这是性能关键点,答辩时可演示django-debug-toolbar中的 SQL 查询数对比。

3. Vue 前端:实现挂号流程的组件化封装与 Token 鉴权闭环

3.1 初始化 Vue 3 项目并配置 Axios 请求拦截器

使用 Vite 创建项目(比 Vue CLI 更轻量):

npm create vite@latest hospital-frontend -- --template vue cd hospital-frontend npm install

安装 Axios 并创建请求实例(src/utils/request.js):

import 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/,避免生产环境部署时因反向代理路径导致 404;localStorage存 token 是毕业设计常见做法,实际项目应配合 HttpOnly Cookie + CSRF Token。

3.2 封装挂号表单组件,用 Composition API 管理状态

创建src/components/AppointmentForm.vue

<template> <div class="appointment-form"> <h3>在线挂号</h3> <form @submit.prevent="handleSubmit"> <div class="form-group"> <label>患者姓名</label> <input v-model="form.patientName" type="text" required /> </div> <div class="form-group"> <label>就诊医生</label> <select v-model="form.doctorId" required> <option value="">请选择</option> <option v-for="d in doctors" :key="d.id" :value="d.id"> {{ d.name }} ({{ d.department }}) </option> </select> </div> <div class="form-group"> <label>预约日期</label> <input v-model="form.date" type="date" required /> </div> <div class="form-group"> <label>就诊时段</label> <select v-model="form.session" required> <option value="AM">上午</option> <option value="PM">下午</option> </select> </div> <button type="submit" :disabled="loading">提交挂号</button> <p v-if="error" class="error">{{ 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 Router:

npm install vue-router@4

定义路由(src/router/index.js):

import { 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-headers

pip install django-cors-headers

配置settings.py

INSTALLED_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 能提供这些静态文件:

  1. 修改 Vue 的vite.config.js,设置base/static/
export default defineConfig({ base: '/static/', // ... 其他配置 })
  1. 构建前端:
npm run build
  1. dist/重命名为static/,放入 Django 项目根目录(与manage.py同级)

  2. 配置 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 输出目录
  1. 添加 URL 路由(urls.py):
from 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_name='index.html')), ] # 开发环境提供静态文件(生产环境由 Nginx 处理) if settings.DEBUG: urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

注意:TemplateView.as_view(template_name='index.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_mode='STRICT_TRANS_TABLES'", 'charset': 'utf8mb4', }, } }

常见报错及解决:

报错信息原因解决方案
django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module未安装mysqlclientPyMySQL执行pip install mysqlclientpip install PyMySQL,并在__init__.py中添加import pymysql; pymysql.install_as_MySQLdb()
Access denied for user 'hospital_user'@'localhost'MySQL 用户权限不足登录 MySQL 执行:
CREATE USER 'hospital_user'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON hospital_db.* TO 'hospital_user'@'localhost';
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.pymigrations/文件,指出具体字段设计如何消除冗余:

  • 第一范式(1NF):所有字段都是原子值。例如Patient.id_card是 18 位字符串,不存储“张三-110101199001011234”这种组合字段。
  • 第二范式(2NF):非主属性完全依赖于主键。Appointment表主键是id(自增),schedule_datesession都只依赖id,不依赖doctor_idpatient_id的部分。
  • 第三范式(3NF):消除传递依赖。Doctor表中department是外键指向Department表,而非直接存科室名称字符串——这样修改科室名称时只需更新Department表一行,避免Doctor表中多行重复修改。

提示:答辩时可打开 MySQL Workbench,执行SELECT * FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA='hospital_db' AND TABLE_NAME='hospital_appointment';展示外键约束,证明doctor_idpatient_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/?doctor=1" # 3. 更新挂号状态(PATCH) curl -X PATCH http://localhost:8000/api/appointments/1/ \ -H "Content-Type: application/json" \ -d '{"status":"CHECKED_IN"}'

观察响应体是否包含标准字段:

  • 成功时返回HTTP 200 OK201 Created,Body 包含完整对象 JSON;
  • 错误时返回HTTP 400 Bad Request,Body 包含{"detail": "..."}格式错误信息;
  • 未登录时返回HTTP 401 Unauthorized,Header 包含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语句是否用了JOINWHERE索引;
  • Views 标签页:看各视图函数耗时,若AppointmentListView耗时 >200ms,说明select_related未生效或数据量过大;
  • Templates 标签页:检查模板渲染次数,避免在循环中多次调用{{ appointment.doctor.user.username }}导致重复查询。

实操技巧:在AppointmentListView.get_queryset()中添加print(queryset.query)可打印最终 SQL,确认是否生成了LEFT OUTER JOIN而非 N+1 查询。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询