ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Django+Flask+Vue实战:牙科诊所管理系统全栈开发笔记

Django+Flask+Vue实战:牙科诊所管理系统全栈开发笔记 前阵子帮朋友牙科诊所做了一套私人诊治管理系统从需求梳理到前后端联调跑通整个流程走下来项目名里的“django-flask”其实代表着Django主服务加Flask辅助服务的组合形态。这套系统以Python为底座后端用Django处理诊所的核心业务用Flask拆出几个轻量功能前端用Vue搭页面开发工具全程在PyCharm里完成。做完之后我最大的感受是这类管理系统比想象中更适合用来完整梳理全栈开发流程因为你会发现它既有常规的信息管理模块也有牙科独特的东西比如牙位记录可以做出亮点。这篇就从头到尾记一份可复现的实战笔记适合正在做课程设计、毕业设计或者刚入门想练手Django/Vue联调的人参考。1. 项目整体设计与技术选型1.1 业务闭环拆解牙科诊所到底需要什么很多同学拿到这种题目第一反应就是把患者表、医生表、病历表堆在一起然后做成四个页面页面之间互相点来点去。但真正到诊所里去问一圈需求你会发现管理系统的核心永远不是“能把数据存下来”而是能不能支撑诊所的一整条业务闭环。私人牙科诊所的日常流程大概是这样的患者到店或电话预约时间前台在系统里建档案医生接诊时查看患者的既往病史和过敏情况做检查后把诊断结果和治疗方案记录到病历里最后前台按照治疗项目生成账单收钱之后还要安排复诊提醒。拆成功能模块就是四个患者管理姓名、性别、年龄、电话、过敏史、既往病史这是整个系统的数据源头。预约管理按医生、日期、时间段分配号源支持取消和状态流转。诊疗记录记录牙位编号、诊断结果、处理方案、医嘱这是医生每天用得最多的功能。收费账单按治疗项目出账单记录金额、日期、支付状态。业务闭环一旦理顺后面的数据表设计和接口设计都有明确依据。我当时先把这四个模块的关系画清楚再用代码实现。系统做完后前台从建档到收费只花十几分钟流程是通的医生才愿意在项目验收时给好评。1.2 技术选型Django 主服务 Flask 辅助服务的组合为什么把Django和Flask放在同一个项目里这是很多人拿到“django-flask”标题时最疑惑的地方。我的做法是把它们拆成两个独立服务各自跑在不同端口通过业务边界区分。Django在这个项目里的角色是“主力后端”负责患者、预约、诊疗、收费这些核心业务模块。原因很简单这类管理系统本质上是一堆数据模型的CRUDDjango自带ORM、Admin后台、DRFDjango REST Framework生态成熟业务模型再多也能用清晰的app结构管理起来。Flask的角色是“辅助服务”负责三类杂活OCR识别识别患者带来的纸质化验单照片、Excel报表导出导出当月账单、复诊提醒定时消息处理。这些功能有几个共同特点逻辑相对独立改动频率高而且不适合混进Django的模型体系里。如果硬塞进Django项目你会发现在settings、urls、models里夹着一堆跟业务无关的代码动态越来越多的时候任何一个细微改动都要带着整个主服务重新跑测试发布风险很大。Flask天生轻巧单独起一个服务就像拴在旁边的副引擎需要时调用不需要时也不干扰主服务。前端选Vue是因为组件化结构对管理系统很友好。工作台、患者表格、预约日历这些例如“可复用卡片”的部分拆成组件后维护成本明显低于在HTML模板里靠include拼页面。整体技术栈汇总如下技术本项目中的角色选择理由Django DRF主体业务后端ORM和Admin成熟模型管理方便Flask辅助服务轻量适合拆分OCR、报表、定时任务VueVite构建前端框架组件化开发配合Element Plus搭建后台界面MySQL数据库业务数据稳定存储支持多服务连接PyCharmIDE调试、数据库工具、HTTP客户端集于一体做一个判断如果只是为了应付演示Django一套到底也能做完。但想体验“微服务雏形”的实际手感用Flask拆一个辅助服务几乎零成本还能顺便把“django-flask”这个项目名落到实处。2. 数据库设计与业务模型拆解2.1 核心数据表与模型代码数据库是整个系统的基础。我建了用户表、患者表、预约表、诊疗记录表、账单表和排班表。用户表没有用Django默认的User而是定义了一个自定义User模型通过AbstractUser扩展出角色字段。这里有个特别值得说的点牙科诊所里首先要区分医生、护士、前台、管理员几种角色后面判断“医生只能看自己名下的患者”这类权限边界时靠的就是角色字段。Django官方也建议项目一开始就自定义User等数据跑起来再想加角色字段迁移会非常痛苦。下面是项目里的核心模型代码做了精简可参考from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): ROLE_CHOICES ( (admin, 管理员), (doctor, 医生), (nurse, 护士), (reception, 前台), ) role models.CharField(max_length20, choicesROLE_CHOICES, defaultdoctor) real_name models.CharField(max_length50) class Patient(models.Model): name models.CharField(max_length50) phone models.CharField(max_length20) gender models.CharField(max_length10) age models.IntegerField(nullTrue, blankTrue) allergy models.TextField(blankTrue, verbose_name过敏史) medical_history models.TextField(blankTrue, verbose_name既往病史) created_at models.DateTimeField(auto_now_addTrue) class Appointment(models.Model): STATUS_CHOICES ( (pending, 待就诊), (done, 已完成), (canceled, 已取消), ) patient models.ForeignKey(Patient, on_deletemodels.CASCADE) doctor models.ForeignKey(User, on_deletemodels.CASCADE, limit_choices_to{role: doctor}) date models.DateField() time_slot models.CharField(max_length20, verbose_name时间段) reason models.CharField(max_length200, blankTrue) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultpending)诊疗记录表比普通病历表特殊的地方在牙位字段。我用了FDI牙位标识法恒牙编号范围是11到48每个牙位都用两位表示法存储比如11是右上第一恒磨牙48是右下第三磨牙。这样存下来前端拿到编号可以直接换算成牙位图里的坐标显示在对应象限。诊断和处理方案放TextFiled因为牙科医生的记录文本会比较长。2.2 关系设计与权限边界四张核心表的关系是这样患者一对多预约患者对应多条诊疗记录诊疗记录里的医生关联用户表。账单表单独记录项目和金额一份治疗里可能有多个收费项所以账单拆成“账单主表账单明细”更规范但咖啡工作室项目为了简化用单表保存JSON数组也不是不行关键看演示深度。权限边界这块我的做法是视图层过滤。医生登录后访问患者列表只返回自己接诊过的患者前台登录后可以新建和修改患者、预约、账单管理员不做业务过滤看全部数据。为什么过滤逻辑不写在序列化器里因为Detail接口和List接口走的是不同的数据路径如果只过滤了列表而漏掉详情账期很容易出现“医生能通过URL猜出别的患者资料”这种越权问题。用DRF的话最省事的写法是在ViewSet里重写get_queryset()方法from rest_framework import serializers, viewsets from .models import Patient class PatientSerializer(serializers.ModelSerializer): class Meta: model Patient fields __all__ class PatientViewSet(viewsets.ModelViewSet): queryset Patient.objects.all() serializer_class PatientSerializer def get_queryset(self): qs super().get_queryset() if self.request.user.role doctor: qs qs.filter(appointment__doctorself.request.user).distinct() return qs这样一改医生打开患者列表时系统自动把“没给自己挂过号的患者”挡在查询外面数据边界直接留在查询集里统一处理。3. 后端双服务实现核心流程3.1 Django 主体DRF 接口与业务场景创建项目时我分成两个Django appusers用户和认证和clinic患者、预约、诊疗、账单。app切分的逻辑很简单能独立复用的放users跟诊所业务强相关的放clinic剩下跨模块的公共逻辑比如翻页、权限判断写进基础视图类里。settings里需要安装的包主要有这几个django、djangorestframework、django-cors-headers、mysqlclient连接MySQL时要处理编译问题用PyMySQL兜底也可以。DRF配置里我打开了Token认证因为管理系统不需要太重的OAuth流程Token认证足够支撑登录态。接口设计时没有把五个模块都写成统一的ModelViewSet。预约接口因为涉及到查重和时间判断我改成了generic视图加自定义action。超网请求方过来查询“某医生某天是否还有号”前端需要的是一个结果不需要额外判断逻辑直接走router默认的list接口配合filter即可。Django自带的Admin后台在这个项目的开发期帮了很大忙。测试数据根本不用写SQL运行迁移后在Admin里点一点就可以录入几个医生账号和患者信息前端联调时直接拿着这些人名写页面逻辑效率比后端造测试数据的接口高得多。3.2 Flask 辅助OCR、报表导出与定时提醒Flask服务的路径规划和Django是平行的。我在Flask里配置了三个核心路由分别对应OCR、账单导出、定时提醒三个场景。OCR这个功能是给诊所实打的场景患者带着纸质化验单或者以前拍的X光报告前台拿手机拍照传进来希望快速提取文字录入系统。用pytesseract做本地识别方便演示不用额外注册三方账号。Flask里接收图片POST返回识别文字给前端。报表导出也是常用功能前端点击“导出当月账单”后端直接生成xlsx文件返回给浏览器下载。用openpyxl生成Excel相比让前端手搓表格要省事很多from flask import Flask, jsonify, request, send_file import openpyxl from io import BytesIO app Flask(__name__) app.route(/api/export_bills, methods[POST]) def export_bills(): data request.get_json() wb openpyxl.Workbook() ws wb.active ws.append([患者姓名, 项目, 金额, 日期]) for item in data[bills]: ws.append([item[patient], item[item], item[amount], item[date]]) bio BytesIO() wb.save(bio) bio.seek(0) return send_file(bio, as_attachmentTrue, download_namebills.xlsx)定时提醒这部分Flask里用轻量线程或者简单schedule库去做每小时扫表把“预约时间是明天但状态还是pending”的患者拉出来生成提醒记录。正式环境一般会换成Celery任务队列但一个小诊所的项目用Flask挂个后台线程足够跑。Django和Flask之间不需要互相调用接口前端直接向两个服务分别请求即可。Django跑在8000端口Flask跑在5000端口两个端口互不干扰。4. 前端 Vue 页面开发与前后端联调4.1 项目搭建、依赖安装与环境配置前端我用Vue 3 Vite相比Vue CLIVite启动速度快体积也轻对课程设计和毕业设计来说更友好。创建项目的方式很简单npm create vitelatest dental_front -- --template vue cd dental_front npm install npm install element-plus axios vue-router piniaElement Plus负责表单、表格、弹窗这些常规组件。登录后进入工作台我用router配置了这几个页面工作台、患者管理、预约管理、诊疗记录、账单管理。页面之间靠侧边栏切换。安装完成后有几个环境配置点容易踩坑需要在项目跑起来之前处理好Vue Router使用Hash模式还是History模式我给管理系统用了History模式配合前端路由的base路径配置否则刷新时404。Pinia用来存登录用户的token和角色信息页面里根据角色控制侧边栏菜单显隐比如护士登录就隐藏账单入口。Element Plus按需引入与否项目小就全量引入省去按需配置的复杂度。搜热搜词时看到很多人卡在“vue安装及环境配置”上实际上就是三步确认Node版本在16以上跑npm install装依赖再跑npm run dev起开发服务器。Node版本太低时Vite会报兼容错误最简单的方法是用nvm切换到较新版本。4.2 核心页面与牙位图组件页面布局比较常规我就不贴全部代码了。值得单独说的是牙位图组件毕竟普通病历系统一般没有这个但牙科系统里这是最有辨识度的功能点。我用SVG画了上下两排恒牙每个牙齿生成一个可点击的图形元素根据FDI编号定位到对应的象限和位置。右上、左上、右下、左下四个区域对应1、2、3、4象限牙位编号后一位从1到8。前端拿到tooth_no后把它拆成象限和牙序再换算成SVG坐标。医生在诊疗记录页点击某个牙位后当前选中的tooth_no自动填入表单的“牙位”字段同时该牙位在图上高亮这样医生看完一眼就知道患者哪些牙齿处理过。这个交互设计看起来不起眼但真正在诊所里使用时非常顺手。生成PDF正文时有个小心得浏览器展示患者上传的化验单图片没问题但如果是PDF附件用img标签是不认的页面上一片空白。正确做法是用iframe或object标签让浏览器内置预览器显示这相当于白捡一个PDF预览功能不需要额外装插件。4.3 axios 封装和跨域代理前端页面写完后需要把接口请求统一封装。我在src目录下建了一个request.js设置axios实例的baseURL为/api拦截器里从Pinia读取token放进请求头后端返回业务错误码时统一弹提示。开发阶段的跨域不像大家想的那么复杂不需要后端开CORS硬扛前端用Vite的代理转发就能解决。vite.config.js里这样配置export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true }, /flask: { target: http://127.0.0.1:5000, changeOrigin: true, rewrite: (path) path.replace(/^\/flask/, ) } } } })前端发/api/patients请求Vite代理把请求转给Django的8000端口发/flask/export_bills请求代理把/flask前缀换成空字符串后转发给Flask的5000端口。这样浏览器看到的请求都是同源的自然没有跨域报错。5. 开发环境配置与 PyCharm 实用技巧5.1 Python 环境与虚拟环境搭建很多人问为什么总是被报ModuleNotFoundError: No module named django多半是Python环境没配对。下载Python时记得勾选“Add Python to PATH”然后在PyCharm里新建项目时选择虚拟环境venv。venv的好处是让每个项目都有独立的依赖空间互不污染装坏了也只需要删除重建一个venv文件夹。安装依赖时如果网络不好建议换国内镜像源。命令行里连用几条pip install -i https://pypi.tuna.tsinghua.edu.cn/simple django djangorestframework django-cors-headers pip install -i https://pypi.tuna.tsinghua.edu.cn/simple flask flask-sqlalchemy openpyxl pytesseract安装在venv里的包只属于当前项目所以PyCharm底部的Terminal要确认当前解释器是venv下的Python不要用系统自带的全局Python。5.2 PyCharm 的多服务运行配置与调试技巧这个项目同时起Django、Flask和Vite三个进程PyCharm里用多个Run Configuration切换很方便。Django配置用manage.py作为脚本参数填runserver 127.0.0.1:8000Flask配置选Python文件填app.py路径环境变量里设FLASK_ENV为developmentVite就不多说了在Terminal里直接npm run dev即可。调试的时候我最依赖PyCharm的两个功能断点跳过和数据库工具。Django请求打进ViewSet后在中间件位置下断点可以直接查看序列化器的query和用户角色字段排查权限过滤比打印日志快很多。数据库工具则可以直接看到表结构、跑SQL验证某条预约是否存在冲突不用停下来开着命令行。关于PyCharm版本的问题社区版对纯Python开发完全够用不需要折腾特殊处理。如果学校能申请教育账户专业版可以免费使用实在不行社区版跑这套项目也不会少任何核心功能。别把时间浪费在找破解上面把精力放在功能实现上更值。6. 常见问题与排查心得6.1 跨域、代理与端口疑难联调阶段最常遇到的报错是Access-Control-Allow-Origin。大部分时候不是我上面说的同源代理没配好而是前端页面里把axios的baseURL写成了http://127.0.0.1:8000/api绕过了Vite的代理。出现这个情况时先按F12打开Network标签看请求URL的完整地址跟vite.config.js里的代理规则对照一下问题基本就清晰了。还有一个被忽略的场景前端和后端不在同一台电脑上跑比如后端在远程服务器前端在自己电脑代理不管用必须后端迁出CORS方案或后端网关上配置跨域推荐头。针对开发环境我给Django装了django-cors-headers配置文件里加白名单即可。6.2 Django 与 Flask 共库的坑Flask连同一个MySQL数据库时我踩过一个比较隐蔽的坑Flask端用裸pymysql还是SQLAlchemy直接决定后续维护顺不顺。裸pymysql写起来直接但连接如果不释放一段时间后数据库连接数会被占满而且Django和Flask对事务的处理方式不一样两边同时写同一个表时容易遇到死锁。建议Flask端统一用flask-sqlalchemy。配置好数据库URI后业务代码从db.session操作数据和Django ORM的心智模型接近。两边的字符集也要保持一致都是utf8mb4否则中文在导出报表的时候会出现乱码。6.3 时间、预约冲突与依赖版本预约功能有一类bug让人排查到头痛前端传送的日期格式是2025-06-15T10:00后端用的是ISO格式字符串在存储时直接报错。解决办法很简单Django的DateField只接受2025-06-15这样的字符串前端提交前把时间做一次格式化处理就行。预约冲突的校验逻辑我用OR条件在创建时判断同一医生、同一日期、同一时间段的记录里只要存在status不是canceled的记录就返回“该时段已被预约”。注意不要用objects.filter(axx).filter(bxx)写安放条件之间的OR组合要用django.db.models.Q对象。依赖版本这块Django 4.x DRF 3.14的搭配比较稳。不要盲目装最新版因为DRF发布频率不如Django稳定有时候刚更新的版本和最新的Django不兼容。项目里建议用requirements.txt锁定当前能跑通的版本这样切换电脑重装环境时能一次性还原。这整个项目开发下来我越来越确定一个结论学习Django和Vue最好的方式不是看一大摞教程而是把一套业务闭环做完整。当你管理完患者、预约、诊疗、收费这四个模块的接口和页面脑子里那套关于全栈开发的模糊认知会一下变得具体。如果后面要扩展我会把这个系统的预约提醒做成微信模板消息再把牙位图接上智能识别让系统在现实诊所里用起来更有价值。
返回列表