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

资讯详情

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

Django 4.2 + Vue 3 医疗系统:挂号分诊与结构化电子病历实战

Django 4.2 + Vue 3 医疗系统:挂号分诊与结构化电子病历实战 简介本资源是一套基于Python全栈技术的医疗预约与诊断系统完整开发包面向Web开发初学者及医疗信息化实践者解决传统线下挂号流程长、医患信息不同步、诊疗数据难追溯等痛点。系统采用Django构建稳健后端含53个核心py文件与1个初始化SQL脚本Vue实现响应式前端147个vue组件63个js逻辑文件15个css样式文件MySQL存储患者档案、预约记录、诊断结果等关键业务数据辅以HTML/CSS/JS完成界面渲染整体结构清晰、模块解耦度高。压缩包共689个文件涵盖159个SVG图标、101个JPG/PNG素材、1个MP4演示视频、多个bat一键部署脚本如init_sql.bat、run.bat及说明文档总大小105.17MB。目前已有83人学习下载读者可直接运行调试、理解前后端分离架构落地细节掌握医疗场景下的用户权限控制、实时预约状态更新、检查报告动态展示等典型功能实现方案。1. 这不是又一个“前后端分离模板”而是一套能跑通挂号、分诊、电子病历与医生接诊闭环的 Django Vue 医疗系统你在网上搜“Django vue 医疗系统”大概率会看到一堆空壳项目前端只有登录页后端 API 返回{code: 0, data: []}数据库 schema 缺失字段requirements.txt里写着Django2.2—— 而真实医疗场景要处理的是患者实名认证后预约指定科室/医生/时段候诊队列自动排序医生端实时查看当前叫号历史诊断记录上传检查报告PDF/影像缩略图生成结构化电子病历ICD-10 编码、主诉、现病史、诊断结论字段可编辑且所有操作需符合《电子病历系统功能应用水平分级评价标准》中对操作留痕、数据不可篡改的基本要求。本项目源码包.zip正是围绕这些刚性需求构建Django 4.2 提供强校验的 RESTful 接口与 admin 后台Vue 3 Composition API 实现响应式预约看板与诊断表单MySQL 存储带外键约束的患者-医生-预约-病历四维关系模型。适合正在落地基层医院信息化、互联网诊疗平台 MVP 或高校课程设计的学生团队——它不教你怎么写 Hello World而是告诉你当患者点击“预约张主任周三上午”时后端如何锁住该时段、校验医生排班、生成唯一预约号并推送短信当医生在 Vue 页面勾选“高血压3级 很高危”系统如何自动关联 ICD-10 编码I10并写入病历结构体。2. 用 Django 4.2 构建医疗领域强约束后端从模型设计到 REST API 安全暴露医疗数据敏感性决定了后端不能只做 CRUD。本系统在 Django 层做了三层硬性控制模型层强制字段约束、视图层权限隔离、API 层操作审计。下面拆解关键实现路径。2.1 医疗核心模型设计用models.py定义可审计的实体关系医疗业务的本质是状态流转因此模型必须体现“不可逆”与“可追溯”。例如Appointment预约模型不直接存status字符串而是用IntegerChoices枚举# models.py class Appointment(models.Model): class Status(models.IntegerChoices): PENDING 0, 待确认 CONFIRMED 1, 已确认 CHECKED_IN 2, 已签到 IN_PROGRESS 3, 诊疗中 COMPLETED 4, 已完成 CANCELLED -1, 已取消 patient models.ForeignKey(Patient, on_deletemodels.PROTECT) doctor models.ForeignKey(Doctor, on_deletemodels.PROTECT) schedule_slot models.ForeignKey(ScheduleSlot, on_deletemodels.PROTECT) status models.SmallIntegerField(choicesStatus.choices, defaultStatus.PENDING) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue) # 关键每次状态变更必须记录操作人医生或管理员 last_updated_by models.ForeignKey( settings.AUTH_USER_MODEL, on_deletemodels.SET_NULL, nullTrue, blankTrue )提示on_deletemodels.PROTECT防止误删医生/患者导致预约数据孤儿last_updated_by字段配合 Django 自带的LogEntry可追溯谁在何时将预约从PENDING改为CONFIRMED。再看MedicalRecord电子病历模型它不是自由文本字段而是结构化 JSONFieldDjango 4.2 原生支持class MedicalRecord(models.Model): appointment models.OneToOneField(Appointment, on_deletemodels.CASCADE) # 主诉、现病史等字段独立存储便于后续 NLP 分析或报表统计 chief_complaint models.TextField(max_length500, blankTrue) history_of_present_illness models.TextField(blankTrue) diagnosis_icd10 models.CharField(max_length10, blankTrue) # 如 I10 diagnosis_text models.CharField(max_length200, blankTrue) # 如 原发性高血压 # 检查报告附件关联多个 FileField实际存于 media/records/ 下 attachments models.ManyToManyField(Attachment, blankTrue)2.2 用 Django REST Framework 构建带业务规则的 API单纯序列化模型不够。例如预约创建接口/api/appointments/必须校验该时段是否已被约满、患者当日是否已预约同科室、医生是否处于停诊状态。这些逻辑写在serializers.py的create()方法中# serializers.py class AppointmentCreateSerializer(serializers.ModelSerializer): class Meta: model Appointment fields [patient, doctor, schedule_slot] def create(self, validated_data): slot validated_data[schedule_slot] # 校验时段是否超限每个时段最多3个预约 if Appointment.objects.filter( schedule_slotslot, status__in[Appointment.Status.PENDING, Appointment.Status.CONFIRMED] ).count() 3: raise serializers.ValidationError(该时段预约已满) # 校验患者当日同科室预约数防黄牛 today timezone.now().date() same_dept_count Appointment.objects.filter( patientvalidated_data[patient], doctor__departmentvalidated_data[doctor].department, schedule_slot__datetoday, status__in[Appointment.Status.PENDING, Appointment.Status.CONFIRMED] ).count() if same_dept_count 2: raise serializers.ValidationError(同一科室每日最多预约2次) # 创建预约并设置初始状态 instance super().create(validated_data) instance.status Appointment.Status.PENDING instance.save() return instance2.3 权限与审计用 Django Admin 和自定义中间件加固生产环境医疗系统必须满足“谁操作、何时操作、操作什么”的审计要求。本项目启用 Django 内置django.contrib.admin并做两处增强Admin 界面定制在admin.py中隐藏敏感字段如患者身份证号明文仅显示脱敏后字符串# admin.py admin.register(Patient) class PatientAdmin(admin.ModelAdmin): list_display [name, gender, age, id_card_masked, created_at] readonly_fields [id_card_masked] # 只读显示 def id_card_masked(self, obj): if obj.id_card: return obj.id_card[:6] * * 8 obj.id_card[-4:] return id_card_masked.short_description 身份证号脱敏操作日志中间件在middleware.py中记录关键 API 请求# middleware.py class AuditMiddleware: def __init__(self, get_response): self.get_response get_response def __call__(self, request): if request.path.startswith(/api/appointments/) and request.method POST: # 记录预约创建行为 if request.user.is_authenticated: AuditLog.objects.create( userrequest.user, actionCREATE_APPOINTMENT, target_idrequest.POST.get(schedule_slot), ip_addressget_client_ip(request) ) return self.get_response(request)参数说明本项目取值DEBUG开发模式开关False生产环境强制关闭SECRET_KEY加密密钥从.env文件读取不提交 GitDATABASE_URLMySQL 连接串mysql://user:passlocalhost:3306/medical_dbALLOWED_HOSTS允许访问的域名[hospital.example.com, www.hospital.example.com]3. 用 Vue 3 Pinia 实现高交互医疗前端从预约看板到结构化病历编辑Vue 端不是静态页面堆砌而是围绕医生和患者两类角色构建状态驱动 UI。核心挑战在于如何让医生在 3 秒内完成一次完整接诊答案是状态机驱动 组件复用。3.1 路由与权限守卫按角色加载不同布局系统使用 Vue Router 4 的meta字段标记路由权限并在router/index.js中统一守卫// router/index.js const routes [ { path: /doctor/dashboard, name: DoctorDashboard, component: () import(/views/doctor/Dashboard.vue), meta: { requiresAuth: true, role: doctor } }, { path: /patient/appointments, name: PatientAppointments, component: () import(/views/patient/Appointments.vue), meta: { requiresAuth: true, role: patient } } ] router.beforeEach((to, from, next) { const user useUserStore() if (to.meta.requiresAuth !user.isAuthenticated) { next({ name: Login }) } else if (to.meta.role user.role ! to.meta.role) { next({ name: Forbidden }) // 403 页面 } else { next() } })3.2 预约看板组件用 Composition API 实现实时队列刷新医生端首页Dashboard.vue需展示“当前叫号”、“待接诊列表”、“今日已完成”。为避免轮询浪费资源采用 EventSourceSSE监听后端推送!-- views/doctor/Dashboard.vue -- script setup import { ref, onMounted, onUnmounted } from vue import { useAppointmentStore } from /stores/appointment const appointmentStore useAppointmentStore() const currentCall ref(null) const pendingList ref([]) onMounted(() { // 建立 SSE 连接监听 /api/sse/doctor/{id}/queue const eventSource new EventSource(/api/sse/doctor/${appointmentStore.doctorId}/queue) eventSource.onmessage (e) { const data JSON.parse(e.data) if (data.type CURRENT_CALL) { currentCall.value data.payload } else if (data.type PENDING_UPDATE) { pendingList.value data.payload } } // 组件卸载时关闭连接 onUnmounted(() { eventSource.close() }) }) /script注意Django 后端需提供/api/sse/doctor/{id}/queue视图使用StreamingHttpResponse持续发送 JSON 数据每条消息以data: {...}\n\n格式分隔。这比 WebSocket 更轻量且天然支持浏览器自动重连。3.3 结构化病历编辑器用 JSON Schema 动态渲染表单电子病历字段非固定需支持不同科室扩展。本项目采用json-editor/json-editor库根据后端返回的 Schema 动态生成表单// composable/useMedicalRecord.js export function useMedicalRecord() { const schema ref({ type: object, properties: { chief_complaint: { type: string, title: 主诉, maxLength: 500 }, diagnosis_icd10: { type: string, title: ICD-10 编码, enum: [I10, E11.9, J45.909], // 从 API 获取动态枚举 enumNames: [原发性高血压, 2型糖尿病, 未特指的哮喘] } } }) const formData ref({}) const saveRecord async () { try { await api.post(/api/medical-records/, { appointment_id: route.params.id, ...formData.value }) ElMessage.success(病历保存成功) } catch (err) { ElMessage.error(保存失败 err.response?.data?.detail) } } return { schema, formData, saveRecord } }4. 在 Linux 服务器上部署 Django Vue 医疗系统Nginx Gunicorn MySQL 生产配置本地开发能跑通不等于生产可用。医疗系统对稳定性、HTTPS、并发承载有硬性要求。本节给出宝塔面板用户可直接复用的部署方案也兼容纯命令行。4.1 后端部署Gunicorn Supervisor 管理 Django 进程Django 官方明确不推荐用runserver上线。本项目使用 Gunicorn 作为 WSGI 服务器配置文件gunicorn.conf.py# gunicorn.conf.py import multiprocessing bind 127.0.0.1:8000 bind_address 127.0.0.1:8000 workers multiprocessing.cpu_count() * 2 1 worker_class sync worker_connections 1000 max_requests 1000 max_requests_jitter 100 timeout 30 keepalive 2 preload True daemon False raw_env [DJANGO_SETTINGS_MODULEmedical.settings.production]启动命令需先激活虚拟环境# 安装 gunicorn pip install gunicorn # 启动后台运行 gunicorn -c gunicorn.conf.py medical.wsgi:application # 或用 supervisor 管理推荐 echo [program:django] command/opt/venv/bin/gunicorn -c /var/www/medical/gunicorn.conf.py medical.wsgi:application directory/var/www/medical userwww-data autostarttrue autorestarttrue redirect_stderrtrue stdout_logfile/var/log/medical/django.log /etc/supervisor/conf.d/medical.conf supervisorctl reread supervisorctl update supervisorctl start django4.2 前端部署Vue CLI 打包后由 Nginx 静态托管Vue 项目执行npm run build生成dist/目录关键配置在vue.config.js中指定反向代理路径// vue.config.js module.exports { outputDir: dist, publicPath: /static/, // 与 Django 的 STATIC_URL 一致 devServer: { proxy: { /api: { target: http://127.0.0.1:8000, // 指向 Django 后端 changeOrigin: true } } } }Nginx 配置/www/wwwroot/hospital.example.com/conf/nginx.confserver { listen 80; server_name hospital.example.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name hospital.example.com; ssl_certificate /www/server/panel/vhost/cert/hospital.example.com/fullchain.pem; ssl_certificate_key /www/server/panel/vhost/cert/hospital.example.com/privkey.pem; location / { root /www/wwwroot/hospital.example.com/dist; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /media/ { alias /var/www/medical/media/; expires 1h; add_header Cache-Control public, immutable; } }提示/media/路径必须指向 Django 的MEDIA_ROOT确保患者上传的检查报告 PDF 能被正确访问add_header Cache-Control对静态资源启用缓存降低带宽压力。4.3 数据库与安全加固MySQL 隔离与 Django 密码策略医疗数据存储必须满足最小权限原则。创建专用数据库用户-- 登录 MySQL CREATE DATABASE medical_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER medical_applocalhost IDENTIFIED BY StrongPassw0rd!; GRANT SELECT, INSERT, UPDATE, DELETE ON medical_db.* TO medical_applocalhost; FLUSH PRIVILEGES;Django 设置中启用密码强度校验settings.pyAUTH_PASSWORD_VALIDATORS [ {NAME: django.contrib.auth.password_validation.UserAttributeSimilarityValidator}, {NAME: django.contrib.auth.password_validation.MinimumLengthValidator, OPTIONS: {min_length: 10}}, {NAME: django.contrib.auth.password_validation.CommonPasswordValidator}, {NAME: django.contrib.auth.password_validation.NumericPasswordValidator}, # 自定义禁止使用身份证号后6位 {NAME: medical.validators.NoIDCardTailValidator}, ]自定义校验器示例# validators.py from django.core.exceptions import ValidationError from django.contrib.auth.password_validation import CommonPasswordValidator def NoIDCardTailValidator(password, userNone): if user and user.patient_profile and user.patient_profile.id_card: tail user.patient_profile.id_card[-6:] if tail in password: raise ValidationError( 密码不能包含身份证号后6位, codeid_card_tail_in_password )5. 验证医疗系统合规性的三个关键动作数据一致性检查、操作留痕回溯、HTTPS 强制跳转部署完成后不能只测“按钮能不能点”而要验证是否满足医疗信息化基本合规要求。以下是上线前必须执行的三项技术验证。5.1 用 Django Shell 检查核心数据链路完整性进入 Django shell模拟一次完整预约-接诊-归档流程验证外键约束与状态流转# python manage.py shell from medical.models import Patient, Doctor, ScheduleSlot, Appointment, MedicalRecord # 1. 创建测试患者 p Patient.objects.create(name张三, id_card11010119900307251X, phone13800138000) # 2. 获取今日张主任的首个可约时段 slot ScheduleSlot.objects.filter( ... doctor__name张主任, ... datedate.today(), ... statusScheduleSlot.Status.AVAILABLE ... ).first() # 3. 创建预约 appt Appointment.objects.create(patientp, doctorslot.doctor, schedule_slotslot) appt.status 0 # PENDING # 4. 医生接诊并生成病历 appt.status Appointment.Status.IN_PROGRESS appt.save() mr MedicalRecord.objects.create( ... appointmentappt, ... chief_complaint头晕3天, ... diagnosis_icd10I10, ... diagnosis_text原发性高血压 ... ) # 5. 检查是否生成了审计日志 from django.contrib.admin.models import LogEntry LogEntry.objects.filter(object_idappt.id).count() 3 # 创建预约、修改状态、创建病历各1条5.2 用 curl 验证 HTTPS 强制跳转与 API 权限隔离医疗系统严禁 HTTP 明文传输。用命令行验证# 测试 HTTP 是否自动跳转 HTTPS curl -I http://hospital.example.com/api/appointments/ # 应返回HTTP/1.1 301 Moved Permanently # Location: https://hospital.example.com/api/appointments/ # 测试未登录访问 API curl -X GET https://hospital.example.com/api/appointments/ # 应返回HTTP/1.1 401 Unauthorized # {detail:身份认证信息未提供。} # 测试患者 token 访问医生专属接口 TOKENeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... curl -H Authorization: Bearer $TOKEN \ https://hospital.example.com/api/doctor/dashboard/ # 应返回HTTP/1.1 403 Forbidden # {detail:您没有执行此操作的权限。}5.3 用 Chrome DevTools 检查前端敏感信息泄露风险打开医生端页面执行以下检查Network Tab筛选XHR查看所有请求 URL 是否含?token或?secret参数应全部通过Authorization: Bearer xxx传递Application Tab → Local Storage确认无patient_id_card、doctor_license_no等明文存储Console Tab执行console.log(JSON.stringify(window.__INITIAL_STATE__))检查初始化数据是否脱敏如身份证号应为110101******251XElements Tab右键任意患者姓名检查 DOM 中是否存在>configureWebpack: { optimization: { minimize: true, splitChunks: { chunks: all } } }本文还有配套的精品资源点击获取
返回列表