1. 为什么选择ThinkPHPUniapp开发巡检系统最近三年我参与过7个工业巡检系统的开发其中5个都采用了ThinkPHPUniapp的技术组合。这种搭配就像咖啡配奶糖——ThinkPHP提供稳定的后端支撑Uniapp则让前端开发事半功倍。去年给某水务集团做的泵站巡检系统从零开始到上线只用了45天客户拿着手机在小程序里完成巡检时直呼比原来的纸质登记快了三倍。传统巡检的痛点太明显了纸质记录容易丢失、数据统计滞后、异常响应慢。而我们的解决方案是ThinkPHP后端用RBAC权限控制确保数据安全定时任务自动生成工单Uniapp前端一次开发可编译成微信/支付宝/百度小程序GPS定位拍照水印防作弊私有化部署所有数据留在客户内网特别适合电力、水务这些敏感行业实测下来这种技术栈有三大优势开发成本比Java系低40%左右跨平台特性让运维人员用自己熟悉的APP操作PHP的快速迭代能力适合频繁的需求变更2. 系统架构设计实战2.1 数据库核心表设计先来看最关键的6张表结构简化版# 巡检点表 CREATE TABLE inspection_points ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT 点名称, qr_code varchar(100) NOT NULL COMMENT 唯一二维码, position point NOT NULL COMMENT GPS坐标, fence_radius int(11) DEFAULT 50 COMMENT 电子围栏半径(米), PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; # 检查项表 CREATE TABLE check_items ( id int(11) NOT NULL AUTO_INCREMENT, point_id int(11) NOT NULL, name varchar(100) NOT NULL COMMENT 如压力表读数, type enum(bool,number,text) NOT NULL, standard_value varchar(255) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;实际项目中我建议添加这些优化为position字段建立空间索引加速附近点位查询使用Redis缓存高频访问的巡检计划大文件如巡检照片建议用MinIO对象存储2.2 后端API开发技巧ThinkPHP控制器里最关键的三个接口// 获取待办任务列表 public function tasks() { $userId Request::instance()-param(user_id); $tasks TaskModel::where(user_id, $userId) -where(status, 0) -with([point, checkItems]) -select(); return json($tasks); } // 提交巡检结果 public function submit() { $data input(post.); Db::startTrans(); try { TaskModel::update([status 1], [id $data[task_id]]); ResultModel::insertAll($data[results]); if ($data[has_problem]) { WorkOrderModel::create($data[work_order]); } Db::commit(); } catch (\Exception $e) { Db::rollback(); return json([code 500, msg 提交失败]); } return json([code 200]); }踩过的坑提醒一定要用事务处理结果提交图片上传要压缩到800KB以内接口频率限制防止刷单3. Uniapp前端开发详解3.1 跨平台适配方案在uniapp的pages.json里要配置多端差异化{ pages: [ { path: pages/task/list, style: { navigationBarTitleText: 巡检任务, app-plus: { titleNView: { buttons: [{ text: \ue60a, fontSrc: /static/iconfont.ttf }] } } } } ] }我总结的适配经验微信小程序用wx.xxx原生APIAPP端用plus.xxx调用设备能力H5版要考虑低版本浏览器兼容3.2 电子围栏功能实现关键代码在task.vue组件中export default { methods: { checkPosition() { uni.getLocation({ type: gcj02, success: (res) { const distance this.getDistance( res.latitude, res.longitude, this.task.point.lat, this.task.point.lng ); if (distance this.task.point.fence_radius) { uni.showToast({ title: 请移动到巡检点范围内, icon: none }); } } }); }, getDistance(lat1, lng1, lat2, lng2) { // 简化版Haversine公式计算距离 const rad (d) d * Math.PI / 180.0; const a rad(lat1) - rad(lat2); const b rad(lng1) - rad(lng2); return 2 * 6378.137 * Math.asin(Math.sqrt(Math.pow(Math.sin(a/2),2) Math.cos(rad(lat1))*Math.cos(rad(lat2))*Math.pow(Math.sin(b/2),2))) * 1000; } } }实测发现三个优化点安卓机定位需要开启高精度模式iOS端首次定位要申请always权限最好加入连续5次定位取中间值的逻辑4. 企业级功能扩展4.1 离线模式实现在manifest.json中配置{ app-plus: { distribute: { plugins: { storage: { version: 1.0, provider: dcloud.io } } } } }关键实现步骤巡检前预加载任务数据到localStorage使用indexedDB存储图片等二进制数据网络恢复后自动同步服务端4.2 可视化数据分析推荐使用ECharts实现// 在vue中引入按需打包的echarts import * as echarts from ../../components/echarts/echarts.simple.min; export default { mounted() { this.initChart(); }, methods: { initChart() { const chart echarts.init(this.$refs.chart); chart.setOption({ tooltip: { trigger: axis }, xAxis: { data: [周一,周二,周三] }, yAxis: { type: value }, series: [{ name: 异常数, type: bar, data: [5, 8, 12] }] }); } } }企业最爱的三个报表巡检完成率热力图设备异常趋势图工单响应时长排名5. 部署与性能优化5.1 服务器配置建议推荐的最低配置腾讯云2核4G突发性能实例足够CentOS 7.6 PHP 7.4MySQL 5.7 Redis 6.x我的nginx配置片段location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi.conf; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # 解决大文件上传 client_max_body_size 50m; fastcgi_read_timeout 300; }5.2 小程序分包策略在manifest.json中配置{ mp-weixin: { optimization: { subPackages: true }, subPackages: [ { root: pagesA, pages: [ task/list, task/detail ] } ] } }性能优化实测数据首包体积从1.8M降到1.2M启动时间缩短40%页面切换更流畅6. 常见问题解决方案6.1 扫码慢问题排查上周刚帮客户解决的案例现象华为P40扫码要5秒以上排查发现是二维码图片带渐变背景使用了非标准的纠错等级解决方案改用纯白背景黑点阵纠错等级设为H尺寸放大到15cm×15cm6.2 照片上传失败处理典型的错误处理流程uni.uploadFile({ url: /api/upload, filePath: tempFilePaths[0], name: file, formData: { task_id: this.taskId }, success: (res) { if (res.statusCode 413) { this.compressImage(tempFilePaths[0]); } }, fail: (err) { if (err.errMsg.includes(timeout)) { this.retryUpload(); } } });三个保底方案自动压缩到80%质量分块上传离线暂存后重试