医院设备报修管理系统实战:微信小程序+Flask全流程开发

📅 发布时间:2026/9/30 9:04:18
医院设备报修管理系统实战:微信小程序+Flask全流程开发
上个月去一家二甲医院办事碰巧看到设备科老师还在用手工台账登记设备维修一台心电监护仪报修电话打到设备科值班员先在纸上记一笔再翻通讯录找负责的维修工程师修完之后补一张三联单。整个过程全靠人肉驱动漏单、催办、扯皮是常态。其实这种场景完全可以用一套轻量系统解决这也是我做微信小程序Python Flask医院设备报修管理系统的起因。这套系统用微信小程序做报修入口后端用Flask提供数据服务数据库用轻量级的SQLite起步覆盖设备台账、报修提交、派单、维修、确认关闭的完整闭环。如果你正打算做个类似的后勤管理小系统或者学校课程设计想选这个方向这篇文章应该能帮你少走不少弯路我会把数据库设计、状态流转、关键接口、小程序端踩坑一次讲透。1. 先看现状医院设备维修为什么漏单、拖单、扯皮1.1 传统报修方式的四个典型问题我做这个项目之前先在几家医院设备科蹲过需求发现设备报修混乱的根源不是人懒而是流程没有工具支撑。第一个问题是报修渠道太散护士发现监护仪坏了可能会打电话、发微信、找护士长转告、甚至等维修师傅路过时口头说一句渠道一多漏单概率就大大增加。设备科也没有统一的受理入口无法确认这件事到底有没有被登记。第二个问题是信息严重不完整。报修人打电话通常只会说XX病区某个机器坏了但设备编号、型号、故障现象、是否影响患者安全这些关键信息很难在电话里问全。维修工程师到了现场才发现带错配件或者压根不知道设备在哪个房间来回折腾几次维修效率极低。第三个问题是过程黑盒。工单交给谁了、修到哪一步了、预计什么时候好科室人员完全不知道只能反复打电话催。遇到夜班或者周末值班报修后没人跟进护士只能干等严重的时候会影响患者检查这就是我为什么在系统设计里把状态可跟踪当作第一优先级。第四个问题是数据没有沉淀。纸质单据堆在柜子里设备一年坏几次、每次维修花了多少钱、哪些型号故障率最高这些问题没人能快速回答。医院采购新设备和报废旧设备时需要这些统计数据做支撑没有数据的设备管理基本靠拍脑袋。1.2 系统要解决的核心角色与场景明确痛点之后我把系统的用户抽象成三种角色。第一类是报修人通常是科室护士、技师或者医生他们的核心诉求是用最快的方式把故障报上去并且能随时看到处理进度。第二类是维修工程师他们需要接单、处理、填写维修结果也希望报修单里就带上设备信息和位置减少沟通成本。第三类是维修主管或设备科管理员他们要能派单、督办、驳回还要能看统计报表。围绕这三个角色系统的核心流程就清晰了科室人员通过小程序扫码或选择设备填写故障描述和照片一键提交维修主管在小程序或后台看到新工单指派给对应工程师工程师接单后开始维修完工后填写维修记录报修人收到完成通知后确认关闭工单。整个流程里所有操作都有记录每一笔状态变化都能追溯。如果你想快速验证这个项目完全不用把功能做得特别大先把这条主链路跑通就已经解决了医院最痛的报修跟踪问题。后续再慢慢加设备台账、统计报表、备件管理这些扩展功能都来得及。2. 技术选型这套组合是怎么定下来的2.1 前端为什么是微信小程序以及 uniapp 要不要用前端选型时我对比过三个方案传统H5网页、微信小程序、原生App。H5虽然开发快但在医院场景里打开路径太长用户要先找浏览器、输网址或者翻收藏夹原生App更麻烦要安装、要升级、要适配iOS和Android两套。微信小程序最大的优势是免安装、在微信里扫码即用而且医院内部人员基本人人都有微信培训成本几乎为零。小程序内部还有一个选择用原生语法开发还是用 uniapp 这类跨端框架。如果你是只做微信小程序我的建议是直接原生。原因很简单uniapp 的抽象层确实能同时输出微信小程序、App、H5但代价是你要多学一套语法遇到底层兼容问题时排查链路更长。我一个朋友用 uniapp 做过类似的管理系统到后面发现自定义导航栏、扫码组件这些需求在原生实现非常简单在跨端框架里却经常要写条件编译。当然如果你们单位明确要求后续还要出安卓或iOS版那用 uniapp 也合理核心里不要放太多平台相关的东西就行。我们这个项目专注微信小程序所以选了原生。2.2 后端为什么选 Flask 而不是 Django后端我选了Python Flask很多人会问为什么不用Django毕竟Django自带Admin后台和ORM功能更全。我的理由有三点。第一这个项目本质是给几十个内部用户用的轻量系统并发量很低核心诉求是接口清晰、开发快速、部署简单Flask的路由和视图写法更直白适合小团队快速迭代。第二Flask的扩展机制很灵活需要数据库就用Flask-SQLAlchemy需要登录认证就用PyJWT一个背包一个包地加不会像Django那样一开始就给你一套重型体系。第三医院后续大概率要做设备数据分析和报表Python的计算生态是现成的Flask项目里直接就能接pandas和matplotlibDjango里也能做但没必要绕这个弯。另外提一句数据库选择。项目刚开始我用的SQLite零配置文件一个文件就是一个库开发和演示阶段特别方便。等真实部署到院内服务器再迁移到MySQL也不难因为SQLAlchemy这个ORM层把换数据库的成本降得很低改一下连接串大部分代码不用动。2.3 整体架构与请求链路整套系统跑起来之后请求链路是这样的用户在微信小程序里点按钮小程序通过wx.request或wx.uploadFile把数据发到Flask后端Flask经过路由分发给对应的视图函数视图函数操作SQLAlchemy模型读写数据库再把JSON结果返回给小程序端渲染。图片这类文件先存到服务器的static/uploads目录数据库里只保存访问路径小程序端用完整URL直接显示。这个架构里没有复杂的消息队列也没有微服务就是一个典型的单体应用。做修报修系统这种内部工具单体架构反而是最稳的一台低配服务器甚至一台普通电脑就能跑出问题排查也简单。先把单体做好等真有性能瓶颈了再拆也不迟。3. 数据库与状态流转设计报修单的命脉3.1 四张核心表怎么建数据库设计是整个系统最关键的部分我拆到后面发现四张表就能覆盖主流程。第一张是用户表users字段包括id、用户名、密码哈希、姓名、角色reporter/engineer/admin、所属科室、联系电话。角色字段决定了操作权限所以单独拎出来后面做权限校验时直接判断。第二张是设备表devices字段有设备编号、名称、型号、品牌、所属科室、存放位置、购置日期、设备状态正常/故障/维修中/报废。设备编号建议用规整的编码规则比如科室缩写加流水号扫描枪和扫码登录都要靠它。第三张是报修单表repair_orders这是核心表字段包括主键、报修单号、设备外键、报修人外键、故障描述、故障图片路径、紧急程度普通/紧急/特急、状态、指派工程师外键、报修时间、受理时间、完成时间、确认时间、评价内容。状态字段非常重要后面单独说。第四张是维修日志表repair_logs记录每一次处理动作。字段包括日志id、报修单外键、操作人、动作类型受理/派单/接单/维修中/完成、处理说明、更换配件、创建时间。这张表的存在是为了让整个流程可追溯也方便以后统计工程师工作量。我用SQLAlchemy来定义模型关联关系通过外键和relationship来管理。设计时有个小经验不要为了省事把所有信息塞进一张表报修单和维修日志分开后查询报修单时只需要主表看历史记录时再查日志表逻辑清晰很多。3.2 报修状态机从提交到闭环的六个状态状态设计是报修单的灵魂我最后定了六个状态待受理、已派单、维修中、已完成待确认、已关闭、已驳回。报修人提交后进入待受理维修主管看到后指派工程师变成已派单工程师点击开始维修变成维修中完工填写结果变成已完成待确认报修人确认无误后变成已关闭整个流程闭环。如果主管觉得工单信息有问题或者不需要维修可以直接驳回备注原因。这个状态机一定要在代码层面做约束不能允许任意跳转。比如已关闭的工单不能重新变成维修中这需要后端接口里对当前状态做校验非法操作直接返回错误。我在开发早期犯过一个错状态字段用普通字符串前端传什么存什么结果出现了维修中跳到待受理这种诡异情况后来统一加了状态转移校验才解决。紧急程度可以单独设一个优先级字段对流程正常运行没什么影响但在排序和提醒时有价值。急诊和ICU报修跟普通门诊的优先级肯定不一样列表页按紧急程度倒序展示维修主管一眼就能看到哪些必须先处理。3.3 权限矩阵与操作边界由于三种角色操作范围不同我在每个接口里都做了角色校验而不是只在前端隐藏按钮。前端隐藏按钮只是体验优化后端校验才是安全底线。报修人可以创建工单、查看自己创建的工单、确认完成和评价工程师可以查看指派给自己的工单、修改工单状态、填写维修日志管理员可以查看所有工单、派单、驳回、管理设备台账。这里有个细节值得说报修人确认完成之前应该把维修结果推送给他让他有时间检查设备是否真的正常。很多系统忽略这一步工程师填完就直接关单容易引发扯皮。多一步待确认状态让报修人参与闭环体验会好很多。医院设备科老师跟我反馈过这个最后一公里确认非常实用避开了很多维修质量纠纷。4. Flask 后端实现报修单 API 从零到可跑4.1 项目目录与依赖准备后端项目我习惯用应用工厂模式组织目录结构清晰扩展起来也方便。核心文件包括run.py入口、config.py配置、extensions.py放db实例、models目录放表模型、api目录按业务域拆成蓝图、utils目录放通用工具。依赖方面环境里装Flask、Flask-SQLAlchemy、Flask-CORS、PyJWT这几个核心包就够了没有任何多余组件。config.py里比较关键的就是数据库连接串和文件上传目录配置。SQLite的连接串写法是sqlite:///hospital_repair.db文件上传路径要确保目录存在我习惯在应用启动时用os.makedirs自动创建uploads文件夹。另外Flask的SECRET_KEY必须设置后面签名和session都会用到不要用默认值。4.2 核心模型定义设备模型和报修单模型是两个最核心的类我把关键代码拎出来说明。RepairOrder表通过device_id关联Devices表通过reporter_id和engineer_id关联Users表外键关系建好之后查询报修单时能直接带出设备名称和报修人信息避免小程序端多次请求。from extensions import db class RepairOrder(db.Model): __tablename__ repair_orders id db.Column(db.Integer, primary_keyTrue) order_no db.Column(db.String(32), uniqueTrue, nullableFalse) device_id db.Column(db.Integer, db.ForeignKey(devices.id)) reporter_id db.Column(db.Integer, db.ForeignKey(users.id)) engineer_id db.Column(db.Integer, db.ForeignKey(users.id), nullableTrue) fault_desc db.Column(db.Text, nullableFalse) image_path db.Column(db.String(255)) priority db.Column(db.String(20), defaultnormal) status db.Column(db.String(20), defaultpending, indexTrue) report_time db.Column(db.DateTime, defaultdb.func.now()) accept_time db.Column(db.DateTime) finish_time db.Column(db.DateTime) confirm_time db.Column(db.DateTime) device db.relationship(Devices, backreforders) reporter db.relationship(Users, foreign_keys[reporter_id], backrefreported_orders) engineer db.relationship(Users, foreign_keys[engineer_id], backrefassigned_orders)报修单号生成我用的是日期随机数的拼接方式形如202506071530001234保证可读性和唯一性。如果你担心并发下随机数冲突可以再加一个事务内查询但内部系统并发不高日期加四位随机数基本够用。4.3 提交报修单接口的实现细节提交报修单是使用频率最高的接口我在这里加了双重校验。第一重校验设备和报修人是否存在第二重校验故障描述不能为空。报修人默认从请求的token里解析不从前端传参里拿能防止别人冒用身份提交。创建成功之后通过OrderNo和时间拼接返回完整的JSON结构小程序端拿这个数据跳转详情页。bp.route(/orders, methods[POST]) login_required def create_order(): data request.get_json() device db.session.get(Devices, data.get(device_id)) if not device: return jsonify(code400, msg设备不存在) if not data.get(fault_desc): return jsonify(code400, msg故障描述不能为空) order_no generate_order_no() order RepairOrder( order_noorder_no, device_iddevice.id, reporter_idg.user.id, fault_descdata[fault_desc], image_pathdata.get(image_path), prioritydata.get(priority, normal), statuspending ) db.session.add(order) db.session.commit() return jsonify(code0, data{order_id: order.id, order_no: order_no})这个接口看起来简单但有几个细节要提醒。request.get_json()在Content-Type不对时可能返回None所以要判空db.session.get是SQLAlchemy 2.0推荐的写法比query.get更好返回结构我统一用code0表示成功非0表示业务错误小程序端通过code判断逻辑比HTTP错误码更直观。4.4 图片上传处理小程序临时文件的关键点报修单里最麻烦的就是图片上传。小程序端先通过wx.chooseMedia选择图片拿到的是一个临时文件路径tmp_path再通过wx.uploadFile上传到Flask。Flask端接收时用request.files.get(file)这个file对象来自Werkzeug拿到的原始文件名不能直接用因为secure_filename会把中文字符转成空字符串导致文件名变成非法值。稳妥的做法是用uuid重新生成文件名保留原始扩展名。这样既避免中文文件名问题也避免文件名碰撞。保存路径按日期分目录比如uploads/202506/方便后续做文件归档和清理。bp.route(/upload, methods[POST]) def upload_image(): file request.files.get(file) if file is None: return jsonify(code400, msg未接收到文件) ext file.filename.rsplit(., 1)[-1].lower() if . in file.filename else jpg if ext not in [jpg, jpeg, png, gif, webp]: return jsonify(code400, msg不支持的图片格式) filename f{uuid.uuid4().hex}.{ext} today datetime.now().strftime(%Y%m) upload_dir os.path.join(current_app.config[UPLOAD_FOLDER], today) os.makedirs(upload_dir, exist_okTrue) file.save(os.path.join(upload_dir, filename)) return jsonify(code0, data{url: f/static/uploads/{today}/{filename}})小程序端上传时报文大小也要防一下Flask默认的MAX_CONTENT_LENGTH不设的话有人传个几十兆的视频会把服务器拖垮。我在配置里加了一行MAX_CONTENT_LENGTH 5 * 1024 * 1024超过5MB直接拒绝上传。4.5 接单并发防护条件更新解决抢单问题闸机式工单到了派单环节会有一个并发问题两个工程师同时点击接单按钮如果代码是先查状态再更新状态两个请求都查到待受理就会同时把这个单抢到自己名下数据就乱了。解决方案是条件更新把状态判断和状态更新放进同一条UPDATE语句里数据库行锁会保证只有一个人能成功。result RepairOrder.query.filter_by( idorder_id, statuspending ).update({ status: dispatched, engineer_id: g.user.id, accept_time: datetime.now() }) db.session.commit() if result 0: return jsonify(code409, msg该工单已被其他人处理请刷新列表)这里的result是受影响的行数如果已经有工程师抢先处理result就是0我们直接返回冲突提示。这种方法在内部分布式锁还没引入时是解决简单抢单问题性价比最高的方案。类似的逻辑也用在工程师点击开始维修和完成维修上凡是有状态跳转的接口都用它防止重复操作。5. 微信小程序端把报修入口做到足够快5.1 页面骨架与路由小程序端我规划了五类页面报修列表、报修提交、报修详情、消息通知、个人中心。底部TabBar放三块待办、报修、我的待办页展示当前用户相关的工单列表报修页是提交入口我的页面放个人资料和科室信息。这样设计逻辑很简单用户打开小程序第一个看到的就是自己关心的工单状态。路由上tab页用tabBar切换报修详情页用普通页面push。从列表页点进详情页时传order_id详情页再通过接口加载完整数据。这里注意小程序页面栈深度限制是10层如果用户连续翻了很多详情页会卡住我一般会在详情页提供一个返回首页的按钮从根页面重建跳转避免栈溢出。5.2 请求封装与登录态管理我在utils/request.js里统一封装了wx.request把域名、超时时间、公共头都放进去每个业务接口只需要关心自己的url和参数。登录态用token机制用户首次进入用微信的wx.login拿到code后端再用code换openid并签发JWT之后每次请求都在header的Authorization字段带上token。const request (url, method, data) { const token wx.getStorageSync(token) return new Promise((resolve, reject) { wx.request({ url: getApp().globalData.baseUrl url, method: method, data: data, header: { Authorization: Bearer token }, timeout: 8000, success: (res) { if (res.data.code ! 0) { wx.showToast({ title: res.data.msg, icon: none }) reject(res.data) return } resolve(res.data.data) }, fail: (err) reject(err) }) }) }开发阶段会碰到一个经典问题小程序真机调试请求不到本地Flask服务。因为手机上的请求走的是局域网而Flask默认只在127.0.0.1监听。解决方法是启动Flask时指定host0.0.0.0然后手机和电脑连同一个WiFi请求地址写电脑的局域网IP。但微信开发者工具默认有校验合法域名的开关开发时要在详情页的本地设置里勾选不校验合法域名否则请求会被当成非法域名拦截。5.3 报修提交流程的实践细节报修提交页是核心交互页面我把功能拆成五步选设备、填故障描述、选紧急程度、拍照上传、提交。选设备可以用wx.scanCode直接扫描设备上的二维码也可以用picker下拉选择科室下已有设备。二维码内容我提前存的是设备编号扫码成功后直接回调到设置设备ID这个步骤省去了手工输入的麻烦。紧急程度这块我用的picker选择器三个选项普通、紧急、特急。特急的话后端会推送模板消息给维修主管并且列表页会标红置顶。故障描述用textarea组件用户要能写清楚设备无法开机屏幕显示代码E3这类信息。拍照用wx.chooseMediacount设为3最多三张前端先压缩再上传降低服务器存储压力。表单提交前要做必填校验设备没选、故障描述为空都不允许提交。校验通过后先传图片再提交工单顺序很重要因为报修单数据需要图片的返回URL。我封装了一个上传函数用Promise包住wx.uploadFile等所有图片都上传完成拿到URL后再统一调用创建工单的接口。5.4 列表下拉刷新与本地缓存报修列表页的数据变化比较频繁用户会不断刷新看状态。我在onPullDownRefresh里重新请求第一页数据同时把结果缓存到storage。缓存时间设30秒30秒内的重复进入先用旧数据渲染等后台请求成功再覆盖刷新。这个策略既保证首屏秒开又不会让数据太陈旧。下拉刷新还有个坑刷新完成后必须调用wx.stopPullDownRefresh来关闭loading动画否则iOS上动画会一直转。另外分页加载我用的传统方案onReachBottom触底加载下一页返回数据里带上has_more字段列表底部做没有更多了的占位提示避免用户一直往上滑也不知道有没有数据。6. 部署上线与踩坑清单6.1 开发调试阶段的坑整个开发过程中我踩了不少坑挑几个典型的说一下。第一个是本地图片能显示但真机显示不了排查半天发现是图片URL写的相对路径/static/uploads开发工具里能拼成完整URL真机上不行。解决办法是引用图片时统一拼接baseUrl或者返给前端的字段里直接存完整URL我是后端在序列化时直接拼好前端拿过来就能用。第二个坑是textarea在iOS上的层级问题这是小程序老bug了。textarea属于原生组件会盖在普通view上面遮挡按钮或弹窗。我提交页的故障描述输入框放在页面底部上来就被键盘顶起来了后来只能用功能降级方案把描述改成一个可点击的文本域点进去跳一个新页面填写再返回多少有点绕但兼容性最稳。第三个坑是时间显示。Flask返回的datetime格式是2025-06-07T15:30:00wxs或者js里做格式化时要处理T字符。我用了一个公共函数把所有后台时间统一转成YYYY-MM-DD HH:mm:ss再展示省了一堆样式问题。6.2 生产部署的最小可行方案项目要真正跑起来我用的最小可行方案是Nginx加Gunicorn加Flask再加SQLite。Gunicorn作为WSGI服务器挂着Flask应用Nginx监听80或443端口做反向代理同时负责转发静态图片请求。Gunicorn启动命令是gunicorn -w 2 -b 127.0.0.1:8000 run:app两个worker就够了毕竟内部系统并发不高。# 安装依赖 pip install flask flask-sqlalchemy flask-cors pyjwt gunicorn # 启动服务 gunicorn -w 2 -b 127.0.0.1:8000 run:app --timeout 60Nginx配置里要把/static/路径指向Flask的uploads目录否则图片请求会被Nginx接管但找不到文件。另外别忘了设置client_max_body_size 5mNginx默认只允许1MB的上传体图片稍微大一点就直接413了这个问题我在上线第二天就遇到了。小程序端正式发布需要在微信公众平台配置request合法域名而且必须是HTTPS。如果你的服务器没有备案和SSL证书测试阶段可以一直用不校验合法域名的模式但正式上线前一定要把域名、证书、备案都搞定否则审核过不了。6.3 从演示项目到院内落地还差哪些事做完这个系统如果你真的想拿到医院里用还有几个点值得补。一是设备台账的初始化需要把医院现有设备批量导入建议后端加一个Excel导入接口让设备科一次性录入而不是一个个手填。二是消息通知能力小程序目前的订阅消息需要用户主动订阅一次才能推送一次用完一次就失效体验不如公众号模板消息但短期内也只能这么用。三是数据统计报表设备故障率、维修及时率、工程师工作量排行这些可以从维修日志表里统计出来后端加几个聚合查询接口前端用简单的表格和柱状图展示。我个人在实际操作中的体会是这类内部管理系统的技术难度真不大最大的价值在于把流程理清楚、把状态流转管住、把权限边界做好。技术上踩的坑都在细节里比如图片上传、并发抢单、域名校验、路径拼接这些坑预期不写代码是永远碰不到的。做完这套系统之后再看类似的报修、工单、审批系统核心逻辑其实都是一套东西换一个行业场景只是改改表和字段而已。这个项目我维护了几个月稳定性和实用性都验证过照着上面的方案做你也能在几天内把一个能跑通全流程的版本搭起来后面再按实际反馈一点一点升级就行。