项目背景:为什么选择UniApp开发企业级应用?
在数字化转型浪潮中,企业需要快速构建跨平台移动应用。传统原生开发成本高、周期长,而UniApp凭借”一次开发,多端发布”的优势,成为企业移动化转型的首选方案。本教程将通过一个完整的智能巡检系统案例,深入讲解UniApp在企业级应用开发中的高级实践。
第一部分:项目架构设计
1.1 系统功能模块
- 巡检任务管理:任务分配、进度跟踪、异常上报
- 设备二维码识别:扫码快速获取设备信息
- 离线数据同步:无网络环境下的数据采集与同步
- 多媒体记录:拍照、录音、视频录制
- 实时定位:GPS轨迹记录与电子围栏
- 数据可视化:巡检数据统计与分析
1.2 项目目录结构设计
smart-inspection/
├── pages/ # 页面文件
│ ├── login/ # 登录页
│ ├── home/ # 首页
│ ├── task/ # 任务相关页
│ │ ├── list.vue # 任务列表
│ │ ├── detail.vue # 任务详情
│ │ └── execute.vue # 任务执行
│ ├── device/ # 设备管理
│ ├── statistics/ # 统计分析
│ └── profile/ # 个人中心
├── components/ # 公共组件
│ ├── inspection-card/ # 巡检卡片
│ ├── media-uploader/ # 多媒体上传
│ ├── offline-manager/ # 离线管理
│ └── map-tracker/ # 地图轨迹
├── store/ # Vuex状态管理
│ ├── modules/
│ │ ├── task.js # 任务模块
│ │ ├── device.js # 设备模块
│ │ └── user.js # 用户模块
│ └── index.js
├── api/ # 接口管理
│ ├── task.js
│ ├── device.js
│ └── upload.js
├── utils/ # 工具函数
│ ├── storage.js # 本地存储
│ ├── validator.js # 表单验证
│ ├── qrcode.js # 二维码处理
│ └── location.js # 定位工具
├── static/ # 静态资源
├── uni_modules/ # 第三方模块
└── manifest.json # 应用配置
第二部分:核心功能实现
2.1 离线数据管理策略
// utils/offline-manager.js
class OfflineManager {
constructor() {
this.dbName = 'inspection_db'
this.storeName = 'offline_data'
this.initDB()
}
// 初始化IndexedDB
async initDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1)
request.onupgradeneeded = (event) => {
const db = event.target.result
if (!db.objectStoreNames.contains(this.storeName)) {
const store = db.createObjectStore(this.storeName, {
keyPath: 'id',
autoIncrement: true
})
store.createIndex('sync_status', 'sync_status', { unique: false })
store.createIndex('created_at', 'created_at', { unique: false })
}
}
request.onsuccess = () => resolve(request.result)
request.onerror = () => reject(request.error)
})
}
// 保存离线数据
async saveOfflineData(data) {
const db = await this.initDB()
return new Promise((resolve, reject) => {
const transaction = db.transaction([this.storeName], 'readwrite')
const store = transaction.objectStore(this.storeName)
const request = store.add({
...data,
sync_status: 'pending',
created_at: new Date().getTime(),
updated_at: new Date().getTime()
})
request.onsuccess = () => resolve(request.result)
request.onerror = () => reject(request.error)
})
}
// 同步数据到服务器
async syncToServer() {
const pendingData = await this.getPendingData()
const results = []
for (const item of pendingData) {
try {
// 调用API同步
const response = await uni.request({
url: '/api/inspection/sync',
method: 'POST',
data: item.data
})
if (response.statusCode === 200) {
await this.markAsSynced(item.id)
results.push({ id: item.id, success: true })
}
} catch (error) {
console.error('同步失败:', error)
results.push({ id: item.id, success: false, error })
}
}
return results
}
// 获取待同步数据
async getPendingData() {
const db = await this.initDB()
return new Promise((resolve, reject) => {
const transaction = db.transaction([this.storeName], 'readonly')
const store = transaction.objectStore(this.storeName)
const index = store.index('sync_status')
const request = index.getAll('pending')
request.onsuccess = () => resolve(request.result)
request.onerror = () => reject(request.error)
})
}
}
export default new OfflineManager()
2.2 智能巡检任务组件
#{{ task.task_number }}
{{ task.name }}
{{ task.device_name }}
({{ task.device_code }})
{{ item.name }}
现场记录
{{ statusText[task.status] }}
export default {
name: 'InspectionCard',
props: {
task: {
type: Object,
required: true,
default: () => ({})
}
},
data() {
return {
expanded: false,
statusText: {
pending: '待执行',
processing: '进行中',
completed: '已完成',
exception: '异常'
}
}
},
computed: {
canSubmit() {
// 检查所有巡检项是否已完成
return this.task.items.every(item => item.value !== undefined)
}
},
methods: {
toggleExpand() {
this.expanded = !this.expanded
},
handleItemSelect(item, value) {
this.$emit('item-change', {
itemId: item.id,
value: value
})
},
async handleSubmit() {
try {
// 检查网络状态
const networkType = await this.checkNetwork()
if (networkType === 'none') {
// 离线保存
await this.saveOffline()
uni.showToast({
title: '已保存到本地,网络恢复后自动同步',
icon: 'success'
})
} else {
// 在线提交
await this.submitOnline()
uni.showToast({
title: '提交成功',
icon: 'success'
})
}
this.$emit('submit-success')
} catch (error) {
uni.showToast({
title: '提交失败',
icon: 'error'
})
}
},
handleException() {
uni.navigateTo({
url: `/pages/exception/report?taskId=${this.task.id}`
})
},
handleMediaUpload(files) {
this.$emit('media-upload', files)
}
}
}
2.3 二维码扫描与设备识别
// pages/device/scan.vue
export default {
data() {
return {
scanResult: null,
deviceInfo: null,
scanHistory: []
}
},
methods: {
// 启动扫码
async startScan() {
try {
// 检查相机权限
const authStatus = await this.checkCameraPermission()
if (!authStatus) {
await this.requestCameraPermission()
}
// 调用扫码功能
const result = await uni.scanCode({
scanType: ['qrCode'],
success: (res) => {
this.scanResult = res.result
this.processQRCode(res.result)
}
})
} catch (error) {
console.error('扫码失败:', error)
uni.showToast({
title: '扫码失败,请重试',
icon: 'none'
})
}
},
// 处理二维码内容
async processQRCode(qrContent) {
try {
// 解析二维码内容(支持多种格式)
let deviceCode = ''
if (qrContent.startsWith('device://')) {
// 自定义协议格式
deviceCode = qrContent.replace('device://', '')
} else if (qrContent.includes('device_id=')) {
// URL参数格式
const params = new URLSearchParams(qrContent.split('?')[1])
deviceCode = params.get('device_id')
} else {
// 直接设备编码
deviceCode = qrContent
}
// 获取设备信息
await this.fetchDeviceInfo(deviceCode)
// 记录扫描历史
this.recordScanHistory(deviceCode)
} catch (error) {
console.error('解析失败:', error)
}
},
// 获取设备详细信息
async fetchDeviceInfo(deviceCode) {
// 先尝试从本地缓存获取
const cachedDevice = this.getCachedDevice(deviceCode)
if (cachedDevice) {
this.deviceInfo = cachedDevice
return
}
// 从服务器获取
try {
const response = await uni.request({
url: `/api/device/${deviceCode}/info`,
method: 'GET'
})
if (response.data.code === 200) {
this.deviceInfo = response.data.data
// 缓存设备信息
this.cacheDeviceInfo(response.data.data)
// 跳转到设备详情页
uni.navigateTo({
url: `/pages/device/detail?id=${deviceCode}`
})
}
} catch (error) {
// 网络异常时使用离线数据
const offlineInfo = await this.getOfflineDeviceInfo(deviceCode)
if (offlineInfo) {
this.deviceInfo = offlineInfo
uni.showModal({
title: '离线模式',
content: '当前为离线模式,显示上次缓存的数据',
showCancel: false
})
}
}
},
// 批量扫码功能
async batchScan() {
this.scanHistory = []
let continueScan = true
while (continueScan) {
const result = await new Promise((resolve) => {
uni.showModal({
title: '批量扫码',
content: '请扫描下一个设备二维码',
confirmText: '继续扫描',
cancelText: '完成',
success: (res) => {
if (res.confirm) {
this.startScan().then(resolve)
} else {
continueScan = false
resolve(null)
}
}
})
})
if (result) {
this.scanHistory.push(result)
}
}
// 批量处理扫描结果
await this.processBatchScan()
}
}
}
第三部分:高级功能实现
3.1 实时定位与电子围栏
// utils/location-tracker.js
class LocationTracker {
constructor() {
this.watchId = null
this.trackPoints = []
this.geofences = []
this.isTracking = false
}
// 开始轨迹记录
startTracking(taskId) {
return new Promise((resolve, reject) => {
if (this.isTracking) {
reject(new Error('已经在轨迹记录中'))
return
}
// 获取当前位置
uni.getLocation({
type: 'wgs84',
success: (res) => {
this.trackPoints = [{
latitude: res.latitude,
longitude: res.longitude,
timestamp: Date.now(),
taskId: taskId
}]
// 开始监听位置变化
this.watchId = uni.onLocationChange((res) => {
if (res.latitude && res.longitude) {
this.addTrackPoint(res, taskId)
this.checkGeofence(res)
}
})
this.isTracking = true
resolve()
},
fail: reject
})
})
}
// 添加轨迹点
addTrackPoint(location, taskId) {
const point = {
latitude: location.latitude,
longitude: location.longitude,
timestamp: Date.now(),
taskId: taskId,
accuracy: location.accuracy || 0
}
this.trackPoints.push(point)
// 限制轨迹点数量,避免内存溢出
if (this.trackPoints.length > 1000) {
this.trackPoints = this.trackPoints.slice(-500)
}
// 保存到本地
this.saveToLocal(point)
}
// 电子围栏检测
checkGeofence(currentLocation) {
this.geofences.forEach(fence => {
const distance = this.calculateDistance(
currentLocation.latitude,
currentLocation.longitude,
fence.latitude,
fence.longitude
)
if (distance <= fence.radius) {
// 进入电子围栏区域
if (!fence.entered) {
fence.entered = true
this.triggerGeofenceEvent('enter', fence)
}
} else {
// 离开电子围栏区域
if (fence.entered) {
fence.entered = false
this.triggerGeofenceEvent('exit', fence)
}
}
})
}
// 计算两点间距离(米)
calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371000 // 地球半径(米)
const dLat = this.toRad(lat2 - lat1)
const dLon = this.toRad(lon2 - lon1)
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(this.toRad(lat1)) * Math.cos(this.toRad(lat2)) *
Math.sin(dLon/2) * Math.sin(dLon/2)
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
return R * c
}
// 停止轨迹记录
stopTracking() {
if (this.watchId) {
uni.offLocationChange(this.watchId)
this.watchId = null
}
this.isTracking = false
// 生成轨迹报告
return this.generateTrackReport()
}
// 生成轨迹报告
generateTrackReport() {
if (this.trackPoints.length === 0) {
return null
}
const report = {
startTime: this.trackPoints[0].timestamp,
endTime: this.trackPoints[this.trackPoints.length - 1].timestamp,
totalPoints: this.trackPoints.length,
totalDistance: this.calculateTotalDistance(),
averageSpeed: this.calculateAverageSpeed(),
points: this.trackPoints
}
return report
}
}
export default new LocationTracker()
3.2 多端适配与性能优化
// utils/platform-adapter.js
class PlatformAdapter {
// 获取平台特定配置
getPlatformConfig() {
const platform = uni.getSystemInfoSync().platform
const configs = {
// 微信小程序配置
'mp-weixin': {
storageKey: 'wx_inspection_data',
uploadMaxSize: 10 * 1024 * 1024, // 10MB
canUseCamera: true,
canUseLocation: true,
supportBackgroundAudio: true
},
// H5配置
'h5': {
storageKey: 'h5_inspection_data',
uploadMaxSize: 20 * 1024 * 1024, // 20MB
canUseCamera: navigator.mediaDevices !== undefined,
canUseLocation: navigator.geolocation !== undefined,
supportBackgroundAudio: false
},
// App配置
'app': {
storageKey: 'app_inspection_data',
uploadMaxSize: 50 * 1024 * 1024, // 50MB
canUseCamera: true,
canUseLocation: true,
supportBackgroundAudio: true,
canUseNativeFeatures: true
}
}
return configs[platform] || configs['h5']
}
// 平台特定的文件上传
async platformUpload(file, options = {}) {
const platform = uni.getSystemInfoSync().platform
const config = this.getPlatformConfig()
// 检查文件大小
if (file.size > config.uploadMaxSize) {
throw new Error(`文件大小不能超过${config.uploadMaxSize / 1024 / 1024}MB`)
}
switch (platform) {
case 'mp-weixin':
return this.weixinUpload(file, options)
case 'app':
return this.appUpload(file, options)
default:
return this.h5Upload(file, options)
}
}
// 微信小程序上传
async weixinUpload(file, options) {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: options.url,
filePath: file.path,
name: 'file',
formData: options.formData,
success: resolve,
fail: reject
})
})
}
// 性能监控
setupPerformanceMonitor() {
// 页面加载性能
uni.onAppRoute((route) => {
const startTime = Date.now()
route.onReady = () => {
const loadTime = Date.now() - startTime
this.reportPerformance('page_load', {
page: route.path,
loadTime: loadTime
})
}
})
// 内存监控
if (uni.getSystemInfoSync().platform === 'app') {
setInterval(() => {
const memory = this.getMemoryUsage()
if (memory > 80) {
this.triggerMemoryWarning()
}
}, 30000)
}
}
// 图片压缩(平台特定)
async compressImage(file, platform) {
if (platform === 'mp-weixin') {
// 微信小程序压缩
return new Promise((resolve) => {
wx.compressImage({
src: file.path,
quality: 80,
success: resolve
})
})
} else if (platform === 'app') {
// App端使用原生压缩
return uni.compressImage({
src: file.path,
quality: 80,
compressedWidth: 1200,
compressedHeight: 1200
})
} else {
// H5使用canvas压缩
return this.h5CompressImage(file)
}
}
}
export default new PlatformAdapter()
第四部分:打包部署与优化
4.1 多端发布配置
// manifest.json 关键配置
{
"name": "智能巡检系统",
"appid": "__UNI__XXXXXX",
"description": "企业级智能巡检解决方案",
/* 小程序配置 */
"mp-weixin": {
"appid": "wx1234567890",
"setting": {
"urlCheck": false,
"es6": true,
"postcss": true,
"minified": true
},
"usingComponents": true,
"permission": {
"scope.userLocation": {
"desc": "用于记录巡检轨迹和位置签到"
}
},
"requiredPrivateInfos": ["getLocation", "chooseLocation"]
},
/* App配置 */
"app": {
"distribute": {
"android": {
"permissions": [
"",
"",
""
],
"abiFilters": ["armeabi-v7a", "arm64-v8a"]
},
"ios": {
"UIRequiresFullScreen": false,
"privacyDescription": {
"NSLocationWhenInUseUsageDescription": "用于记录巡检轨迹",
"NSCameraUsageDescription": "用于拍摄设备照片",
"NSMicrophoneUsageDescription": "用于录制现场语音"
}
}
}
},
/* H5配置 */
"h5": {
"title": "智能巡检系统",
"router": {
"mode": "hash"
},
"publicPath": "./",
"template": "template.h5.html",
"optimization": {
"treeShaking": {
"enable": true
}
}
}
}
4.2 性能优化策略
- 代码分割:使用uni-app的分包加载机制
- 图片优化:实现懒加载和WebP格式支持
- 数据缓存:合理使用本地存储和内存缓存
- 请求合并:批量处理API请求减少网络开销
- 组件异步加载:使用defineAsyncComponent延迟加载
- 渲染优化:避免不必要的重新渲染
总结与展望
通过本实战教程,我们完整构建了一个企业级智能巡检系统,涵盖了从项目架构设计到具体功能实现的全过程。关键收获:
- 架构设计:合理的目录结构和模块划分是项目成功的基础
- 离线能力:IndexedDB和本地存储保障了无网络环境下的可用性
- 多端适配:平台适配器模式有效解决了多端差异问题
- 性能优化:从代码层面到运行时的全方位优化策略
- 可维护性:组件化开发和状态管理提升了代码质量
UniApp在企业级应用开发中展现出强大的生产力,结合Vue3的Composition API和TypeScript,可以构建出更加健壮和可维护的跨平台应用。
// 交互增强脚本
document.addEventListener(‘DOMContentLoaded’, function() {
// 代码块语法高亮和复制功能
const codeBlocks = document.querySelectorAll(‘pre code’);
codeBlocks.forEach(block => {
// 添加复制按钮
const copyBtn = document.createElement(‘button’);
copyBtn.textContent = ‘复制’;
copyBtn.className = ‘copy-btn’;
copyBtn.onclick = function() {
navigator.clipboard.writeText(block.textContent).then(() => {
const original = copyBtn.textContent;
copyBtn.textContent = ‘已复制’;
setTimeout(() => {
copyBtn.textContent = original;
}, 2000);
});
};
const pre = block.parentElement;
pre.style.position = ‘relative’;
pre.appendChild(copyBtn);
});
// 平台切换演示
const platformDemo = document.createElement(‘div’);
platformDemo.innerHTML = `
平台适配演示
`;
document.querySelector(‘.feature’).appendChild(platformDemo);
});
function showPlatformInfo() {
const info = {
userAgent: navigator.userAgent,
platform: navigator.platform,
isWechat: /MicroMessenger/i.test(navigator.userAgent),
isMobile: /Mobile/i.test(navigator.userAgent)
};
document.getElementById(‘platformInfo’).innerHTML = `
${JSON.stringify(info, null, 2)}
`;
}

