☰
微信小程序设备报修系统实战:工单设计、状态流转与上线避坑
2026/10/7 12:22:58 网站建设 项目流程

我们单位一年前也是典型的状态:报修基本靠喊,维修靠等,设备有没有人管全凭师傅的心情。行政群里每天“打印机又卡了”“会议室投屏没信号”刷屏,报修信息淹没在斗图里,师傅挨个打电话确认位置,白跑一趟是常态。后来我把这套基于微信小程序的设备报修系统落地,整个流程才算理顺。这篇文章不聊虚的,就把我从设计到上线踩过的坑、定下来的方案、写出来的关键代码,一次性说清楚。

这套系统能做什么:师生或员工在微信里拍照、选地点、填描述就能提交报修单;维修师傅接到工单后上门处理、回传进度;管理员在后台看到实时数据,按部门、按设备类型统计维修率,月底对着报表做资产盘点。它解决的核心问题是“报修信息从口头转达变成结构化数据流转”,状态可查、责任到人、过程留痕。适合正在做毕业设计、企业内训项目,或者单纯想给单位解决实际问题的开发者参考,前端以微信小程序为主,后端我用的是Node.js + MySQL的经典组合,全文包含完整字段设计、状态机流转逻辑、登录鉴权和图片压缩上传的实测代码。

1. 项目定位与核心设计思路

1.1 三个角色的痛点决定了功能边界

先梳理清楚谁在用这套系统,功能才不会做泛。我把使用方拆成三类人,每类人只给他最必要的操作入口。

报修人(普通员工/学生/住户)要的是“零门槛”:打开小程序就能报,不用下载App,不用记设备编号,拍照传图、选一下所在位置、填一句“什么坏了”就完事。他最关心的是“我报完以后有没有人管”,所以要给他一个单号,能随时看进度状态。

维修师傅要的是“少跑冤枉路”:接单前能看到清晰的位置信息、故障描述、现场照片,避免到现场才发现缺工具、缺配件。他还要能更新状态,比如“已接单”“处理中”“待验收”,让其他人知道活干到哪一步了。

管理员要的是“心里有数”:哪些设备故障频发?哪栋楼报修最多?师傅平均响应时间是多长?这些数据如果靠人工翻聊天记录,永远统计不出来。所以后台必须要有按时间、按区域、按类型的多维筛选报表。

三类角色对应到系统里,就是三种身份、三种页面、三套权限。登录以后后端返回角色字段,小程序首页按角色渲染成不同Tab,这样既保证操作效率,也避免把界面搞复杂。

1.2 为什么选微信小程序,而不是App或网页后台

技术选型这事,我对比过三条路,最终选了小程序,理由很直接:

安装成本为零。单位里的设备报修人群覆盖老中青,让所有人去应用商店搜App并下载注册,门槛太高,一定会有人嫌麻烦就绕开系统走老路子。小程序扫二维码或从微信里直接进入,用完即走,没有心理负担。

身份体系蹭微信。微信提供了完整的登录和手机号快速验证能力,省掉了自建账号体系、短信验证码这些重复工作。用户不用记密码,打开就是“微信一键授权”,对非技术人群极度友好。

消息触达闭环。报修工单状态变更时,我可以用微信订阅消息通知报修人和服务人员,形式上就是微信聊天列表里的服务通知。相比短信通知的成本,这笔钱完全省了。

当然,小程序也有它的局限,最明显的是包体积限制——主包加分包不能超过2MB(现在可以到20MB但需要分包处理),富文本编辑器、复杂图表库不能随便往主包里塞。我的对策是图表类一律走后端生成图片返回,尽量用原生组件,把插件依赖控制在最小范围。

1.3 整体架构与数据流设计

整个系统的数据流其实是一条线性链路:

报修人提交工单 → 写入报修主表 → 管理员或系统自动派单 → 师傅接单 → 处理中 → 完工 → 报修人验收 → 归档入历史台账

前端小程序负责采集和展示,后端提供RESTful API,数据库落盘所有状态。为了简化部署和运维,我没有引入Redis做缓存,报表数据直接查MySQL,量级在几千条工单的应用场景下完全够用。文件存储用云存储(OSS),拍照的图片走前端压缩后直接上传,数据库里只存URL,避免把大字段塞进表里拖慢查询。

数据库表我建了五张核心表:users(用户/角色)、repair_orders(工单主表)、order_status_log(状态变更日志)、devices(设备台账)、feedback(验收评价)。其中工单表是整个系统的核心,下面细说字段设计。

2. 核心功能模块拆解与关键字段设计

2.1 报修单的字段设计决定数据质量

填报表单是用户第一道门槛,字段多一个用户就多一分流失。我最终定了以下提交字段,每个字段都有存在的理由:

字段控件类型是否必填设计说明
设备/设施名称输入框必填自然语言描述,不搞严格台账绑定,降低用户操作成本
故障位置位置选择必填调用微信chooseLocation选点,经纬度自动写入,附带地址文字
故障描述textarea必填限制200字内,预置“无法开机、异响、漏水、接触不良”等常见标签可点选
现场图片图片上传选填最多传3张,前端压缩到约200KB再传云存储
联系方式手机号必填默认从微信号授权获取,用户可修改

这里值得多说一句:为什么设备名称不用半结构化的资产编号。我在一期做过严格绑定,每台设备打二维码标签,绑定固定资产编号。结果发现两个实际问题——第一,公共区域的设备(比如走廊灯、公共打印机)没有专人维护标签,二维码经常破损或被覆盖;第二,用户不知道编号在哪,反而产生逆反情绪。二期的方案妥协成“文字描述 + 位置选点”,系统后台再做一次人工或关键词匹配去关联台账。数据不一定完美,但胜在真实、可持续。从信息完整度来看,一张带经纬度的照片 + 一段描述,已经足够维修人员事先判断大致问题了。

工单主表的动手字段我列一下:

CREATE TABLE `repair_orders` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_no` varchar(32) NOT NULL COMMENT '工单号,如BX20250101001', `reporter_id` bigint(20) NOT NULL COMMENT '报修人ID,关联users表', `reporter_name` varchar(50) DEFAULT NULL, `reporter_phone` varchar(20) DEFAULT NULL, `device_name` varchar(100) DEFAULT NULL, `location_desc` varchar(255) DEFAULT NULL, `latitude` decimal(10,7) DEFAULT NULL, `longitude` decimal(10,7) DEFAULT NULL, `fault_desc` varchar(500) DEFAULT NULL, `priority` tinyint(1) DEFAULT '2' COMMENT '1紧急 2普通 3低', `status` tinyint(1) DEFAULT '0' COMMENT '0待分配 1已接单 2处理中 3待验收 4已完工 5已关闭', `assignee_id` bigint(20) DEFAULT NULL COMMENT '维修师傅ID', `assign_time` datetime DEFAULT NULL, `finish_time` datetime DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_status` (`status`), KEY `idx_create_time` (`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

工单号我采用BX + 年月日 + 三位流水号的格式,例如BX20250115001。这里不要用自增ID直接展示给用户,因为ID会暴露当天总单量,不适合对外展示。生成方式:后端取当天已有单数加一,利用数据库唯一索引防止并发重复。

2.2 工单状态机与派单策略

整个系统的灵魂是工单状态机。我前期吃过亏,一开始只设“待处理 / 已处理”两个状态,结果师傅到了现场发现缺配件,单子一挂好几天,报修人疯狂催单。后来我重新梳理,把状态调成六个,并且记录了状态变更日志:

0待分配 → 1已接单 → 2处理中 → 3待验收 → 4已完工 → 5已关闭

为什么需要“待验收”这个状态?在设备维修场景里,“修完了”应该由使用者说了算。我可以选“修好了,挂单完结”,但可能出现师傅标完工、实际没解决,用户还得重新提单的尴尬。增加一个待验收状态,报修人确认没问题后再点“确认完工”,如果没修好,可以选择“重新打开”,工单直接回到“处理中”,保证流程闭环。

派单策略我做了两种:管理员手动派单和师傅抢单。小场景里抢单效率更高——师傅打开小程序看到待分配列表,根据位置和空闲状态自主认领。大场景(比如几百人报修、几十个师傅)时,抢单容易变成“选择性接单”——简单的单子秒抢,难搞的漏水、电路问题没人碰。所以我做的是手动派单为主、抢单为辅:管理员可以把单压给指定区域负责人,负责人再派给具体师傅。系统后台有一张区域-师傅映射表,如果某个区域只有一个对口的师傅,系统自动派单,否则进入手动池。

2.3 管理后台的统计维度

后台统计不是锦上添花,它是系统上线后获取话语权最关键的模块。我得拿着数据去向行政领导证明系统有用,比如:平均响应时间从一个工作日缩短到2小时,月度故障TOP5设备名单,各楼栋的报修趋势。有了这些,后面申请预算、升级功能才有依据。

我做的是三个维度:

  • 按时间维度:今日报修数、本周处理完成率、月度环比,报表曲线按天聚合。
  • 按区域维度:每栋楼/每个楼层的报修单量和完工率,及时发现设施老化密集区。
  • 按设备类型:故障描述里的关键词分组(投影、空调、门锁、网络),统计重复故障率。

报表实现上,后端SQL做group by,返回前端拿wx-charts或纯canvas绘制。这里需要注意,小程序里直接引图表库包体积大,而且canvas绘图在部分安卓机型上渲染不稳定。我给小程序端只展示数字卡片和简单柱状条(view宽度比例模拟),详细的图表走后台网页端。想在小程序里看详细趋势,就直接让后端生成一张PNG图片返回,效果稳定又省开发量。

3. 实操过程与关键代码实现

3.1 微信登录与手机号授权

小程序登录是典型的wx.login + 后端换 session流程,我的登录函数写成了这样:

// pages/login/login.js async function wxLogin() { const { code } = await wx.login(); if (!code) { wx.showToast({ title: '获取登录凭证失败', icon: 'none' }); return; } const res = await request({ url: '/api/user/login', method: 'POST', data: { code } }); // res.data 里返回 { token, role, nickName, avatarUrl, phone } wx.setStorageSync('token', res.data.token); wx.setStorageSync('userInfo', res.data); // 根据 role 跳转不同首页 wx.switchTab({ url: res.data.role === 'worker' ? '/pages/worker/index' : '/pages/user/index' }); }

后端拿到code后,调微信的jscode2session接口换openid。这个openid就是用户的唯一身份标识,不要拿来当登录token用,应该你自己生成一个sessionToken(我是用uuid)存在后端,返回给前端。因为openid泄露会有一定风险,客户端拿到它没用,而且它也不该被前端感知。

关于手机号授权,需要注意微信在2023年之后收紧了规则:getPhoneNumber得到的code必须配合access_token调用phonenumber.getPhoneNumber接口才能换取明文手机号,而且个人主体小程序没有这个权限,需要企业/组织主体。我在开发调试阶段被这个卡了很久,模拟器里能弹窗,真机上后端报61001错误码。如果你的主体没权限,一个妥协方案是让用户手动输入手机号,做一次短信验证码校验(需要买短信服务,一条几分钱;不验证的话数据质量会明显下降,乱填手机号的用户很多)。

3.2 图片压缩与上传

照片是报修单里最有价值的信息。原始照片动辄3-5MB,直接上传既慢又费用户流量。我在前端做了一层压缩,实际体验从“上传一张等3秒”降到“基本是秒传”,显著提高报修完成率。

// utils/upload.js function chooseAndCompressImage(count = 3) { return new Promise((resolve, reject) => { wx.chooseMedia({ count, mediaType: ['image'], sizeType: ['compressed'], // 先拿微信压缩后的 sourceType: ['album', 'camera'], success: async (res) => { const compressed = []; for (const file of res.tempFiles) { const result = await new Promise((r) => { wx.compressImage({ src: file.tempFilePath, quality: 60, success: (s) => r(s.tempFilePath), fail: () => r(file.tempFilePath) // 压缩失败就退回原路径 }); }); compressed.push(result); } resolve(compressed); }, fail: reject }); }); }

wx.compressImage的quality参数取值0-100,我实测60是最合适的平衡点——画质肉眼无感损耗,体积基本能缩到1/5以下。要注意的是,小程序真机摄像头输出的原图非常大,光一次chooseMedia的sizeType: ['compressed']不够,那个压缩是微信提供的快速压缩,对部分Android机型效果较差。二次wx.compressImage能兜底。

上传我用的是wx.uploadFile,需要注意这个API和普通的wx.request不是一回事,header里的content-type是multipart/form-data,且无法在wx.uploadFile里配置自定义请求头的时候,再关联wx.request的拦截器里的token逻辑。很多人在这里踩坑,说“为什么我登录了,uploadFile还是401”。我的做法是手动给wx.uploadFile加formData里的token,后端从formData取值验权,不走header。

3.3 地理位置定位与顶部导航栏适配

报修地点能不能选,直接决定师傅找不找得到地方。我用的是wx.chooseLocation(需要先wx.getLocation授权),用户在地图上拖拽选点,然后前端把经纬度和地址文字一起提交。这里有一个权限细节:从2022年之后,微信规定wx.getLocation必须在答题申请后方可使用,个人主体不能调wx.chooseLocation。个人开发者小程序如果没有权限,两个解决路径:

  • 用wx.choosePoi替代(需要类目资质);
  • 保存“省市区 + 详细门牌号”文本,经纬度为0,后台人工匹配。

顶部导航栏高度适配是整个小程序里最不显眼但最容易踩的坑。设备的“胶囊”(右上角那个圆形按钮)位置不是固定的,刘海屏和普通屏不一样,要算menuButton的边界。我的方案是写一个通用工具函数:

// utils/nav.js function getNavBarInfo() { const { statusBarHeight } = wx.getWindowInfo(); const menu = wx.getMenuButtonBoundingClientRect(); const navBarHeight = (menu.top - statusBarHeight) * 2 + menu.height; return { statusBarHeight, navBarHeight, menu }; }

然后在自定义导航栏的页面,动态设置占位 view 的高度,避免页面内容被刘海和胶囊盖住。这里有个细节:先用wx.getWindowInfo(),不要用已被废弃的wx.getSystemInfoSync(),后者在新版本基础库已经下线。真机调试时务必用iPhone X系列和带挖孔的安卓机型各测一遍,宽度和高度公式在不同机型上表现差异极大。

3.4 订阅消息通知的实现与限制

微信订阅消息是工单流转通知的免费方案,但有一个非常恶心的限制:一次授权只能发给用户一条通知。用户点击“允许订阅”时,相当于授权你“发送一条一次性消息”。要让用户持续收到通知,必须每次都调wx.requestSubscribeMessage请求授权。

我的策略是“在关键节点请求”:用户提交报修单成功后,立刻弹授权框,请求“工单状态更新提醒”的一次性订阅。这个时间点的用户正处于“期待后续”的状态,授权通过率最高。后台状态流转的关键动作(接单 / 完工)触发时,调用订阅消息接口推送。

// 提交报修成功后选择订阅 async function subscribeAfterSubmit() { try { const res = await wx.requestSubscribeMessage({ tmplIds: ['YOUR_TEMPLATE_ID_REPAIR_STATUS'] }); // res['YOUR_TEMPLATE_ID_REPAIR_STATUS'] === 'accept' 表示用户同意 } catch (e) { // 用户拒绝或基础库版本过低,静默处理即可 } }

推送端后端用cloudbase或原生Node发送订阅消息时,需要传用户的openid和提交时带的page(点击通知后跳转的小程序页面路径,必须是线上存在的页面,且需带参数,比如pages/detail/index?id=123)。多次踩坑后的结论:后端发送订阅消息尽量用微信云开发的云函数来发,API调用频次限制是每分钟10次,足以支撑小范围的报修场景,而且原生cloudbase封装好了openapi调用,省得自己维护access_token的刷新和存储。如果是自建Node服务,需要自己维护access_token的定时刷新,access_token有效期只有2小时,在过期前用定时任务续上。

4. 常见问题与排查技巧实录

4.1 图片上传失败率高的根因治理

上线第一周,报修单图片成功率只有82%,用户传图经常失败。排查下来原因非常清晰:

第一个坑是上传时没有超时设置。微信公众号后台默认上传超时是10秒,但校园弱网环境下3-5MB的图经常超过。压缩后降到200-500KB,10秒内基本没问题。如果你的图片在弱网下还是失败,一种办法是改为“先提交文字,图片后上传”的异步策略,工单状态不阻塞。

第二个坑是iOS和Android的临时文件路径不一致。wx.compressImage返回的路径在某些Android机型上带wxfile://前缀,有的安卓文件管理器能读,有的不能。wx.uploadFile的filePath参数接收的是本地临时路径,如果你拿到tempFilePath后直接丢给wx.uploadFile,在部分安卓上会报file not found。稳妥做法:写完tempFilePath后,先在wx.getFileSystemManager().access()里测一下文件是否存在,如果不存在,要从tempFiles[0].thumbTempFilePath(chooseMedia 自带的缩略图路径)兜底。

4.2 手机号快速验证组件与隐私协议配置

登录时用button open-type="getPhoneNumber"获取手机号,在2023年9月前后微信对隐私协议进行了强校验。如果你没在「小程序后台-设置-服务内容声明-用户隐私保护指引」中声明收集手机号,前端弹授权时微信会直接拦截getPhoneNumber,并报“请先声明隐私协议”。

处理办法是在「小程序管理后台 -> 设置 -> 基本设置 -> 服务内容声明」中勾选并填写收集“手机号”的用途。本地开发时,你在开发者工具的“详情-本地设置”勾选“不校验合法域名、web-view域名、TLS版本以及HTTPS证书”并没有用,隐私协议是硬校验,必须在后台配好。另外调wx.getPhoneNumber所得的code有效期只有5分钟,拿到的手机号解密后,不要明文存数据库,建议MD5脱敏或只存后四位,防止用户隐私数据外泄引发合规问题。

4.3 工单“丢单”问题的排查

“用户明明报修了,师傅说没看到”是运营中最大的信任危机。我排查后发现原因很简单:状态流转时,前端拉列表的接口没有做分页渲染的容错。

小程序端滚动到底部用onReachBottom加载更多,我在第一版没有处理“重复点击导致的重复请求”。用户在列表页快速下拉,同一接口并发请求了两次,后端幂等没做好,导致同样一条单子出现在列表里两次,但status还是0(待分配),看起来就像新报的单没更新。

解决方案:前端加载更多时加一个loading锁,同一时间只允许一个请求发出;后端在做状态流转时增加乐观锁校验:

UPDATE repair_orders SET status = #{newStatus} WHERE id = #{orderId} AND status = #{currentStatus}

如果影响行数为0,说明状态已被别人改过,抛出“工单状态已变动”提示。这个乐观锁机制救了我的列表页至少三次。

4.4 包体积超限与分包策略

小程序主包大小限制是2MB,这是硬上限。我在加完图表库、区域划分配置、地图sdk之后,主包一度逼近1.9MB,每次发布心惊胆战。后来做了分包处理:

  • 主包:只放登录页、首页框架、公共组件、工具函数。
  • 分包A(报修流程):报修表单、图片上传、提交成功页。
  • 分包B(管理后台):工单列表、详情、统计面板。
  • 分包C(个人中心):我的报修、消息通知、帮助反馈。

分包的好处除了压体积,还有加载速度提升——首屏只需要加载主包,用户点进分包的瞬间再加载对应页面。注意tabBar页面不能放在分包里,所以报修工作台我放在了主包,但操作页全部走分包,实际体验很流畅。

关于source size 2612kb exceed max limit 2mb这类报错,直接在app.json里配置:

{ "subpackages": [ { "root": "pages/repair", "pages": ["form/index", "success/index"] }, { "root": "pages/admin", "pages": ["orders/list", "orders/detail", "stats/index"] } ], "preloadRule": { "pages/index/index": { "network": "all", "packages": ["pages/repair"] } } }

preloadRule是指进入主包某个页面后,预先加载对应的分包,这样用户从首页进报修表单时,几乎感觉不到加载过程。

4.5 无法“正式发给别人试用”与真机测试

标题里提到的“微信开发者工具里的小程序怎么发给其他人试用收集反馈”,这是我开发早期最困惑的一件事。工具里你生成的预览二维码有效期只有2分钟,而且只能自己扫码看。正确流程是:

  1. 在微信公众平台开启“开发-开发管理-开发设置-体验版二维码”,把工具上传的版本设置为体验版。
  2. 把体验版二维码发给测试成员,需要先在后台“成员管理”添加他们的微信号为体验成员(限15人)。
  3. 体验版可以与正式版并存,体验数据用的是线上正式环境(如果后端API已经部署在公网)。

注意体验版和开发版调用的API域名校验不一致:开发版在工具里可以不校验域名,但体验版和正式版都会校验request合法域名。在微信公众平台「开发管理-服务器域名」里配置request合法域名、uploadFile合法域名、downloadFile合法域名,必须用HTTPS且证书有效。我本地调试时经常跳过这个,等部署到体验版才报url not in domain list,这个配置别等到最后一刻再处理。

4.6 抓包调试的正确姿势

排查接口问题时,手机真机上的请求没法直接看console。我之前用Charles抓包微信小程序,但小程序的HTTPS证书校验很严格(新版微信基本都把SSL Pinning做了),Charles默认是抓不到小程序的包的,还要花时间配置CA证书到系统信任区,并且Android 7.0以上用户级别的CA证书默认不被信任,操作极其繁琐。实测下来更省事的方案是:

  • 用微信开发者工具的“真机调试”模式,直接在手机上看console和Network面板;
  • 后端加全局请求日志中间件,把每个请求的method + url + body + response + 耗时打印到服务端日志文件,排查问题直接登录服务器看日志;
  • 需要抓HTTP层看请求详情时,在开发者工具里用自带的Network工具最干净。

我一开始走了弯路,在抓包上耗了一整天,后来干脆在后端写了一个debug.log:每进来一个请求,把入参出参都记录下来。生产环境里排查问题效率远高于抓包。

结尾:说点实际的

回头看,整个系统从立项到跑通内部试用,大约用了一个月。核心功能只占20%的代码量,剩下80%的时间全花在适配、鉴权、域名配置、真机兼容这些问题上。如果你是从零开始做,建议先在网上找一个包含用户登录、表格提交、上传图片这些基础功能的小程序模板,在此基础上改业务逻辑,比从空项目手写每个基础组件要快得多。还有两个我实操后最想提醒的点:第一,所有状态流转务必加乐观锁,这能救你命;第二,表单只要超过两个字段,尽早做自动暂存功能,小程序页面切后台太久会被回收,用户填了一半的表单说没就没,流失率极高。这套系统后续还可以往“租借预约”“会议室预订”“耗材申领”的方向扩展,底层的角色权限和工单流程都是通用的,换一批字段就能变成另一套管理系统。

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

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

立即咨询