1. 项目背景与需求拆解
1.1 为什么医院设备报修需要数字化
医院设备的报修场景和普通办公设备完全不同。一台呼吸机停摆,影响的可能是一个ICU床位;一台心电监护仪出问题,护士就得手工记录患者数据。设备科每天要面对几十单报修请求,传统的电话/纸质报修流程里,报修内容靠电话口述、维修进度靠人工追问、设备台账靠Excel表格,三个月下来数据就乱了。
这个项目的核心目标,是把报修这件事变成一条可追踪、可统计、有沉淀的数字化流水线。具体来说,系统要覆盖四个环节:报修提交、审核派单、维修闭环、数据统计。医护人员通过微信小程序发起报修,填写设备编号、故障描述、拍摄现场照片;设备科管理员在后台审核,分派给对应工程师;工程师接单后更新维修状态,填写维修结果;最后报修人确认验收,整条工单闭环。
这套流程解决的最大痛点有三个:一是责任清晰,每一单都有时间戳和操作人;二是进度透明,报修人打开小程序就能看到当前处理到哪一步;三是数据可复用,季度结算时直接导出报表,哪些设备故障率高、哪些供应商保修响应慢,一目了然。
1.2 核心需求画像与角色边界
在设计这套系统之前,需要先把用户画像摆清楚。医院场景下的角色和普通企业报修系统差别很大,至少要拆出四类人:
- 报修人:一线医护人员(护士、技师、医生)。他们的核心诉求是"最快速度把单子发出去",所以小程序端操作要极简,表单不能超过一屏,拍照要支持多张,网络差的时候要能暂存。
- 设备科管理员:负责审核报修单、分派任务、验收结果。他们需要的是批量操作能力,比如同样的故障类型一次性派给对应工程师,以及可视化看板,能按科室、设备类别、紧急程度筛选。
- 维修工程师:可能自己就是医院设备科的人,也可能是第三方维保公司人员。他们关注的是"我的待办单有哪些""维修需要什么配件""这台设备的维修历史"。
- 系统管理员:管人员账号、设备台账、角色权限、基础字典数据。
四类角色的权限边界要清晰:报修人只能看自己提交的单子;工程师只能看分派给自己的单子;管理员能看全部但不能操作维修记录;维修记录一旦提交不可删改,只允许追加工单备注。这些规则直接决定数据库设计和接口权限控制的方向。
2. 技术选型:ThinkPHP与Laravel如何支撑整条业务链
2.1 框架选择:Laravel和ThinkPHP各自适合什么
标题里同时提到ThinkPHP和Laravel,这两个都是国内PHP项目里的主流框架,选型的时候应该按项目的实际情况去权衡。
Laravel的优势在于工程化程度高:artisan命令生成代码骨架、Eloquent ORM操作数据库非常顺手、中间件机制做接口鉴权很干净、队列和定时任务能轻松处理"报修单超时提醒"这类需求。如果你有Composer使用经验,或者团队里有人熟悉Laravel,用它开发这类管理系统的体验很舒服。缺点是学习曲线稍陡,对部署环境有一定要求,PHP版本至少7.4以上,国内虚拟主机环境下配置伪静态需要留意。
ThinkPHP的优势是轻量、上手快、文档和视频资料基本都是中文的,国内高校课程和毕业设计里用得非常多。它的验证器、自动加载、路由配置做得简单直接,一套流程跑下来非常快。对新手来说,ThinkPHP报错信息更直观,调试成本低。
所以我的建议是:如果你是给课程设计或毕设做项目,选ThinkPHP就够了,省下的时间可以投入到业务细节上;如果这是要交付给医院实际运行的商用系统,优先选Laravel,后面加需求、接第三方系统、做自动化测试时,社区生态带来的红利会非常明显。两个框架都支持微信小程序的后端接口开发,本质上没有选错的问题,只有合不合适的差异。
2.2 微信小程序与后端的数据链路设计
微信小程序的架构是前后端分离的:小程序端发HTTPS请求到后端接口,后端返回JSON数据。整个链路上需要重点处理的点有这些:
接口鉴权:小程序不能直接存用户密码,标准的做法是wx.login获取临时code,后端把code发给微信服务器换取openid和session_key,然后签发一个token(建议用JWT)返回给小程序。小程序每次请求都把token放在Authorization请求头里,后端中间件统一校验。
HTTPS要求:微信小程序的生产环境必须走HTTPS,且域名需要在小程序后台配置白名单。开发调试阶段虽然可以在开发者工具里勾选"不校验合法域名",但真机预览时必须用真实域名。
图片上传:报修单要带现场照片,小程序端用wx.chooseMedia选图,然后通过wx.uploadFile传到后端的上传接口,后端接收后存入文件目录,返回文件URL。这里有一个容易踩的坑:上传接口返回的数据不能直接是JSON对象,必须是字符串格式(比如"{"url":"xxx"}"),小程序端要用JSON.parse解析。
数据同步:维修状态变更后,小程序端需要及时刷新。最简单的做法是每次进入页面重新拉取列表;体验更好一点的做法是引入微信订阅消息,状态变更时后端调订阅消息接口推送一条通知。订阅消息需要用户在小程序里主动订阅,一次订阅只能推送一次,这个限制要在设计用户引导时考虑到。
3. 数据库设计与核心接口实现
3.1 数据表拆解与字段设计
整套系统的核心数据表我拆成了7张,建表时遵循了一套原则:所有业务表必须有create_time和update_time;逻辑删除用is_deleted字段而不是物理DELETE;状态字段用tinyint类型,注释里写清楚每个数字代表什么含义。具体设计如下:
user表(用户表):id、username、password(bcrypt加密)、real_name、phone、role(1报修人/2管理员/3工程师)、department_id、avatar、status、is_deleted。这里要注意role字段很关键,所有接口的权限判断都依赖于它。工程师和报修人可以是同一个人吗?实际场景中一个医生在本科室是报修人,不代表他能以工程师身份接单,所以角色要区分openid、session_key、nickname、avatar、gender、role、department_id、created_at。这个表的核心是同时记录微信身份和系统角色,因为小程序端没有传统密码登录,只能用微信登录后绑定角色。工程师登录时可以选绑定哪个角色,由管理员审核。
device表(设备表):id、device_name、device_model、device_no(院内唯一编号)、location、department_id、manufacturer、supplier、warranty_expire、status(1正常/2已报修/3维修中/4报废)、buy_date、is_deleted。设备表是整个系统的基石,报修单必须关联device_id,不允许纯文本输入设备名,避免同一台设备被写成一堆别名。设备编码规则建议用"科室号-分类-序号",比如"ICU-01-0032"。
repair_order表(报修单表):id、order_no(工单号,如BX20250101001)、device_id、reporter_id、assignee_id(指派的工程师)、creator、department_id、fault_desc、fault_type、urgency_level(1普通/2紧急/3特急)、status、appointment_time、created_at、updated_at。状态流转是这张表的核心逻辑,我单独讲。
repair_record表(维修记录表):id、order_id、engineer_id、action(1接单/2开始维修/3申请配件/4完成维修/5退回重报)、remark、images、created_at。这个表是冗余设计,报修单当前状态只存在repair_order表里,但每一次动作都记进这个流水表,后期回溯问题时非常有用。
attachment表(附件表):id、biz_type、biz_id、file_url、file_name、uploader_id、created_at。这个表是通用的,报修单图片、维修结果图片都往这里放,用biz_type区分。
system_config表(系统配置表):id、config_key、config_value、remark。用来放"保修期内自动提醒天数""响应超时小时数"这类可变配置,避免改逻辑要动代码。
3.2 工单状态流转与接口设计
报修工单的状态机是整个系统的"主心骨",它的设计决定了后续所有接口的回传逻辑。我用整型数字来表示状态,每一步的流转都做严格限制:
| 状态值 | 状态含义 | 可流向的状态 |
|---|---|---|
| 10 | 待审核 | 20、90 |
| 20 | 待派单 | 30、90 |
| 30 | 维修中 | 40、50、90 |
| 40 | 待验收 | 50、90 |
| 50 | 已完成 | - |
| 90 | 已驳回/取消 | - |
状态触发的接口对应如下:
- 创建报修单(POST /api/repair/order):报修人填写表单,生成订单状态10,同时调用微信订阅消息接口,提醒管理员处理。
- 审核(PUT /api/repair/audit):管理员通过或驳回,通过后待派单,驳回需要填写原因。
- 派单(PUT /api/repair/assign):管理员将订单指派给指定工程师,系统给工程师推送订阅消息。
- 接单(PUT /api/repair/accept):工程师接单,状态改为30,同时记录开始时间。
- 完成维修(PUT /api/repair/complete):工程师填写维修结果、上传维修图片,状态改为40。
- 验收(PUT /api/repair/acceptance):报修人确认无误,状态改为50完成;如果不通过,退回状态30让工程师返修。
接口统一返回JSON格式:code(0成功/非0失败)、msg、data。前端拿到code之后再做逻辑分支。特别是在状态流转处,后端除了校验token外,还要校验操作人角色,比如只有status为20的订单才允许派单,否则就算你调了接口也会被拦下来。
这个状态机用ThinkPHP里的模型事件和Laravel里的观察者都可以做,推荐把状态变更记录在repair_record表里,方便以后做审计。
4. 小程序端实现要点
4.1 登录流程与手机号获取
微信小程序的登录流程和网页端完全不同,很多第一次接触的人会卡在这一步。登录的完整链路如下:
- 小程序端调wx.login,获得临时code。
- 小程序把code发给自家后端(自己的服务器,不是微信)。
- 后端拿着code + appid + appsecret,请求微信接口
https://api.weixin.qq.com/sns/jscode2session,换回openid和session_key。 - 后端用openid去查user表,如果已存在则直接生成token返回;如果不存在,则在小程序端弹出"绑定账号"页面,让用户选择身份并填写工号等信息。
- 小程序把token存入wx.storage,后续所有请求带上token。
这里有一个关键细节:openid是用户在小程序里的唯一标识,但每次wx.login拿到的code只能用一次,重复使用后端会报"invalid code"。所以代码里千万不要把code缓存起来复用。
至于"获取手机号",微信官方推荐的是button组件设置open-type="getPhoneNumber",用户点击后会在回调里拿到一个加密的phone code。这个code同样需要后端拿session_key去解密(实际上现在推荐直接用后端调接口换取),才能得到真实手机号。要注意的是,这个功能需要小程序已认证(企业/事业单位主体),个人开发者小程序是用不了的。如果没认证,就退回到手动输入手机号的方案,表单校验格式即可。
4.2 报修表单与拍照上传的实现细节
报修表单是整个小程序里最核心的页面,设计上应该把"拍摄故障照片"和"填写故障描述"放在一个页面里,避免跳转。字段我建议控制在:设备编号(可扫描或搜索)、故障类型(下拉选择)、故障描述(textarea)、紧急程度(单选)、照片(最多9张)。
拍照上传这里有两个坑要讲一下:
一是chooseMedia和旧版chooseImage的兼容性问题。基础库2.10.0之后推荐用wx.chooseMedia,它在选择图片时可以同时支持从相册选和直接拍摄,返回的临时文件路径格式略有不同,需要统一做处理。如果项目要兼容老旧微信版本,就得做基础库判断。
二是图片压缩。医院里手机拍的照片动不动就3MB往上,一堆原图传到服务器既慢又占存储。建议前端拿到临时路径后先做一次压缩:用wx.compressImage或者canvas绘图时统一0.75比例输出。实测压缩到宽1200px、quality 80,一张图能控制在300KB左右,清晰度足够看细节。上传时还可以控制并发数,比如一次最多同时传3张,避免小程序同时发起太多网络请求导致阻塞。
表单提交前建议加一个"我要提交"前的确认弹窗,列出本次报修涉及的关键信息,让用户确认后提交。这个交互看起来多余,实际能大幅减少因为误触产生的无效工单。
4.3 导航栏与页面适配细节
微信小程序的导航栏有一个所有新手都会踩的坑:不同手机的胶囊按钮位置不一样,导致自定义导航栏的按钮容易被挤到无语。iPhone顶部是刘海屏+胶囊,安卓手机上胶囊在右侧,底部还有手势条。如果你用了自定义导航栏(navigationStyle: custom),那safe area的计算、状态栏高度获取、胶囊位置获取这三件事必须做对。
获取导航栏高度的标准写法是:
const systemInfo = wx.getWindowInfo() const menuButton = wx.getMenuButtonBoundingClientRect() // 导航栏高度 = 胶囊底部坐标 - 状态栏高度 + 胶囊与顶部的间距 * 2 const navHeight = (menuButton.top - systemInfo.statusBarHeight) * 2 + menuButton.height拿到这个高度后,动态设置占位view的高度,就能保证不同机型页面头部不被遮挡。我个人建议:如果页面结构不复杂,可以直接用原生导航栏,把标题设成"设备报修"就完事;只有需要放自定义操作按钮(比如扫码、筛选)时才用自定义导航栏。
另外小程序包体积限制是2MB(主包),如果项目里引用了大图、声音文件、UI框架,很容易超限。解决方案有三个:图片全部放服务器不走本地打包;UI组件只按需引入,别整个框架塞进来;复杂页面用分包加载,比如把"统计报表"这类低频页面分出去。
5. 实操过程:搭建一套可运行的报修系统
5.1 后端接口开发与配置
我用ThinkPHP为例,走一遍从零搭建的流程(Laravel版本逻辑一致,只是命令不同):
第一步,用Composer创建项目:
composer create-project topthink/think 医疗设备报修系统第二步,配置数据库。在.env文件里填入数据库连接信息,然后创建数据库,执行迁移文件建表。Laravel里用php artisan migrate,ThinkPHP可以用迁移工具或者直接导入SQL。
第三步,写路由和控制器。核心接口按模块划分:AuthController处理登录、DeviceController处理设备、OrderController处理报修单。以"创建报修单"为例,核心逻辑是:
public function createRepair(Request $request) { // 1. 校验参数 // 2. 验证设备是否存在且在保修或正常状态 // 3. 创建repair_order记录,status=10 // 4. 如果有上传图片,处理attachment // 5. 触发推送通知给管理员 // 6. 返回订单号给前端 }这里有两个要注意的点:参数校验要放在最前面;设备状态需要加锁判断,比如并发时同一台设备被同时报修两次,要用事务+乐观锁(device表加version字段)来处理,或者对device_id加唯一索引限制只有"正常"状态设备能发起新报修单。
第四步,写中间件做鉴权。解析请求头里的Authorization,校验token有效期和签名,把解析出的用户信息挂到请求对象上。管理员接口再校验role字段,不是管理员直接返回403。
第五步,对接微信订阅消息。在system_config里配置模板ID、appid、secret,封装一个WechatMessageService,在审核、派单、完成等节点调用。订阅消息的推送次数受限,如果推送失败要记录日志并且后端做兜底,比如在站内信列表里生成一条未读消息。
5.2 小程序端搭建与联调
小程序端我用原生语法(不用uniapp)举例,这样每一步都能对应到工具的具体界面:
第一步,在微信开发者工具里新建小程序项目,AppID填自己的(没认证就用测试号也行,但不支持部分接口)。
第二步,创建request.js封装请求方法,统一带上token、处理状态码、做错误提示:
const request = (url, method, data) => { return new Promise((resolve, reject) => { wx.request({ url: baseUrl + url, method, data, header: { 'Authorization': wx.getStorageSync('token') }, success: (res) => { if (res.data.code === 0) resolve(res.data.data) else if (res.data.code === 401) { /* token过期,跳登录 */ } else { wx.showToast({ title: res.data.msg, icon: 'none' }) } }, fail: reject }) }) }第三步,页面开发。按角色和功能拆页面:登录页、报修列表页、创建报修页、工单详情页、消息通知页、我的页、管理端的工作台页(含审核、派单、统计)。每个页面都遵循data-生命周期-交互方法的顺序,先画静态UI再绑接口。
第四步,真机调试。重点检查:HTTPS是否走通(开发者工具常因关闭校验而掩盖问题);上传接口在真机上能否正常触发;订阅消息能否正常拉起授权弹窗;手机号的getPhoneNumber按钮在不同微信版本上是否兼容。
5.3 部署与上线前的检查清单
部署后端到服务器时,这几项必须逐条确认:
- PHP版本与框架要求匹配(Laravel 9+需要PHP 8以上)。
- 数据库字符集用utf8mb4,避免emoji和生僻字报错。
- 上传目录设置可写权限,并配置好公开访问路径的伪静态规则。
- HTTPS证书部署完成,且微信小程序后台配置的域名要和证书域名完全一致。
- 跨域问题:小程序端不存在浏览器跨域概念,但如果是后台管理Web端也要用,记得在后端配置跨域中间件。
上线前最后走一遍完整流程:报修人提交->管理员收到通知->审核通过->派单给工程师->工程师接单->维修完成上传记录->报修人验收->双方都确认结束后,查一下数据库里repair_record表的记录是否完整,统计报表数字是否对得上。
6. 常见问题与排查技巧实录
6.1 高频问题速查表
我把在实际开发中踩过的坑整理成了一张速查表,这些故障现象和排查手段都是亲自实测过的:
| 异常现象 | 排查思路 | 解决方案 |
|---|---|---|
| 开发者工具能请求接口,真机不行 | 大概率是域名没校验或没配HTTPS | 小程序后台配置合法域名,服务器部署证书 |
| wx.login偶尔code失效 | 用了缓存code或重复提交 | 确保登录逻辑每次都重新wx.login,不存code |
| 上传图片返回undefined | 后端返回了JSON对象,而wx.uploadFile要求字符串 | 后端序列化打包后返回,前端JSON.parse解析 |
| 订阅消息推送失败 | 用户未授权或当天推送次数触顶 | 弹窗引导主动订阅,后端记录失败日志,站内信兜底 |
| 同一设备并发重复报修 | 设备表状态未加锁 | 事务+状态判断,仅"正常"状态可发起 |
| 自定义导航栏按钮错位 | 未适配胶囊按钮高度 | 用getMenuButtonBoundingClientRect计算高度 |
| 工单状态跳变异常 | 前端直接改了状态 | 按状态机校验,非法流转直接拒绝并记录日志 |
6.2 排查日志的实用技巧
调试这类前后端分离的项目,我习惯在三个位置同时打点:小程序端请求日志、后端中间件日志、数据库操作日志。后端日志里除了记录参数和返回结果,还要记录操作人ID、当前时间、请求路径,这样排查问题时能快速定位到对应的那一单。Laravel里有现成的Log facade,ThinkPHP里用trace方法,建议在线上环境开启每日日志轮转,别让日志文件无限膨胀。
一个非常容易踩的坑是时区问题:服务器默认时区如果不是Asia/Shanghai,会导致工单里记录的时间比本地时间差8小时,用户看到的报修时间全乱了。解决方法是框架配置文件里统一设置timezone,同时数据库连接串里也加上时区参数,双保险。
6.3 实用小技巧
最后分享几个我从实际项目中沉淀的细节处理技巧:
报修单号生成规则:不要用自增ID直接做工单号,用了容易被爬虫遍历。建议用日期+随机数,比如BX20250101103,中间带年配置好的。同时要把工单号放在推送消息的标题里,方便用户直接报给设备科。"我那台设备单号是...",沟通效率会快很多。
催单功能:用户体验中很重要的是"报修没人理"引发的焦虑。可以加一个"催一下"按钮,点击后在后端生成一条催促记录,并给管理员推送一条加急通知。这个功能看似简单,但要在后端限流,同一个工单1小时内只能催一次,避免被刷屏。
批量派单:管理员遇到同类型故障多台设备时,手动逐台指派效率很低。在管理端做一个多选框+批量指派,后端循环创建repair_record记录。实测下来,批量派单这个功能是设备科老师最喜欢的功能之一。
设备二维码:这个增值功能属于加分项。给每台设备生成二维码贴纸,贴在设备上,报修人扫码后自动填充设备编号和科室信息,省去手输和搜