From 279cc4a5eafc4f01d392b49cf23be9e42a90f4f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=89=E6=B5=A9=E8=8C=B9?= Date: Thu, 9 Oct 2025 17:22:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E7=A9=BA=E8=B0=83=E5=BC=80=E5=85=B3?= =?UTF-8?q?=E3=80=81=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README-萤石云对接.md | 2 + src/components/EzvizVideoPlayerSimple.vue | 154 +++++- src/pages/environment/index.vue | 484 +++++++++++------- src/pages/visual/index.vue | 74 --- src/utils/mqttDataManager.js | 109 +++-- 萤石云APP对接完整指南.md | 572 +--------------------- 6 files changed, 518 insertions(+), 877 deletions(-) diff --git a/README-萤石云对接.md b/README-萤石云对接.md index 31bce4d..9754181 100644 --- a/README-萤石云对接.md +++ b/README-萤石云对接.md @@ -248,3 +248,5 @@ console.log('组件ref:', this.$refs.playerVideoRef) 需要详细说明?查看 → [萤石云APP对接完整指南.md](./萤石云APP对接完整指南.md) + + diff --git a/src/components/EzvizVideoPlayerSimple.vue b/src/components/EzvizVideoPlayerSimple.vue index 7f2257f..d44dba2 100644 --- a/src/components/EzvizVideoPlayerSimple.vue +++ b/src/components/EzvizVideoPlayerSimple.vue @@ -17,13 +17,33 @@ > - + - H5平台暂不支持 + + + - - + + + + + {{ isPlaying ? '⏸ 暂停' : '▶ 播放' }} + + + 🔄 刷新 + + + + + + + @@ -31,6 +51,7 @@ 🔄 刷新 + {{ loadingText }} @@ -39,6 +60,16 @@ {{ errorText }} + + + + + + @@ -56,7 +87,8 @@ export default { return { platform: '', status: '未初始化', - webviewUrl: '', + webviewUrl: '', // APP平台使用 + iframeUrl: '', // H5平台使用 loading: false, loadingText: '', error: false, @@ -96,12 +128,26 @@ export default { this.status = '加载中' try { + // #ifdef APP-PLUS + // APP平台:使用本地HTML文件 const token = encodeURIComponent(config.accessToken) const url = encodeURIComponent(config.play_url) - // 使用iframe版本(内存占用更小) this.webviewUrl = `/static/html/ezviz-iframe.html?accessToken=${token}&playUrl=${url}` + console.log('[简单播放器] APP平台 - 使用本地HTML文件') + // #endif - console.log('[简单播放器] 使用iframe版本,URL已设置') + // #ifdef H5 + // H5平台:直接构建萤石云iframe URL + this.iframeUrl = 'https://open.ys7.com/ezopen/h5/iframe?' + + 'url=' + encodeURIComponent(config.play_url) + + '&accessToken=' + encodeURIComponent(config.accessToken) + + '&width=100%' + + '&height=100%' + + '&autoplay=1' + + '&audio=1' + + '&controls=1' + console.log('[简单播放器] H5平台 - 直接使用萤石云iframe') + // #endif setTimeout(() => { if (this.loading) { @@ -154,7 +200,13 @@ export default { // 因为iframe播放器不支持直接控制,所以采用重新加载的方式 if (this.isPlaying) { // 暂停:清空URL + // #ifdef APP-PLUS this.webviewUrl = '' + // #endif + // #ifdef H5 + this.iframeUrl = '' + // #endif + this.isPlaying = false this.status = '已暂停' @@ -163,9 +215,23 @@ export default { } else { // 播放:重新设置URL if (this.config) { + // #ifdef APP-PLUS const token = encodeURIComponent(this.config.accessToken) const url = encodeURIComponent(this.config.play_url) this.webviewUrl = `/static/html/ezviz-iframe.html?accessToken=${token}&playUrl=${url}` + // #endif + + // #ifdef H5 + this.iframeUrl = 'https://open.ys7.com/ezopen/h5/iframe?' + + 'url=' + encodeURIComponent(this.config.play_url) + + '&accessToken=' + encodeURIComponent(this.config.accessToken) + + '&width=100%' + + '&height=100%' + + '&autoplay=1' + + '&audio=1' + + '&controls=1' + // #endif + this.isPlaying = true this.status = '播放中' @@ -195,12 +261,31 @@ export default { }) // 先清空再重新加载 + // #ifdef APP-PLUS this.webviewUrl = '' + // #endif + // #ifdef H5 + this.iframeUrl = '' + // #endif setTimeout(() => { + // #ifdef APP-PLUS const token = encodeURIComponent(this.config.accessToken) const url = encodeURIComponent(this.config.play_url) this.webviewUrl = `/static/html/ezviz-iframe.html?accessToken=${token}&playUrl=${url}` + // #endif + + // #ifdef H5 + this.iframeUrl = 'https://open.ys7.com/ezopen/h5/iframe?' + + 'url=' + encodeURIComponent(this.config.play_url) + + '&accessToken=' + encodeURIComponent(this.config.accessToken) + + '&width=100%' + + '&height=100%' + + '&autoplay=1' + + '&audio=1' + + '&controls=1' + // #endif + this.isPlaying = true this.status = '播放中' @@ -244,16 +329,27 @@ export default { .video-webview { width: 100%; - height: 50%; + height: 100%; + position: absolute; + top: 0; + left: 0; } -.h5-tip { - display: flex; - align-items: center; - justify-content: center; +/* H5平台iframe容器 */ +.h5-iframe-container { + width: 100%; height: 100%; - color: white; - font-size: 28rpx; + position: absolute; + top: 0; + left: 0; + background: #000; +} + +.h5-iframe { + width: 100%; + height: 100%; + border: none; + display: block; } .loading { @@ -300,6 +396,36 @@ export default { border-radius: 10rpx; } +/* 错误状态下的控制按钮 */ +.error-controls { + display: flex; + gap: 20rpx; + margin-top: 30rpx; + justify-content: center; +} + +.error-controls .control-btn { + flex: 1; + max-width: 200rpx; + height: 60rpx; + border-radius: 8rpx; + font-size: 24rpx; + font-weight: bold; + color: white; + border: none; + display: flex; + align-items: center; + justify-content: center; +} + +.error-controls .play-btn { + background: linear-gradient(135deg, #46a049 0%, #4caf50 100%); +} + +.error-controls .refresh-btn { + background: linear-gradient(135deg, #2196f3 0%, #21cbf3 100%); +} + /* 控制按钮 */ .control-buttons { position: absolute; diff --git a/src/pages/environment/index.vue b/src/pages/environment/index.vue index 5d34062..cb19f3d 100644 --- a/src/pages/environment/index.vue +++ b/src/pages/environment/index.vue @@ -24,9 +24,9 @@ - + @@ -44,9 +44,9 @@ - + @@ -64,22 +64,39 @@ - + - + ❄️ - 空调目标参数设置 + 空调设置 - 运行中 + {{ acStatusList[acStatus] }} + + + + + + + + 设备控制 + + + + + @@ -259,27 +276,27 @@ export default { cleanlinessProgress: 0, lastUpdate: '暂无数据', temperatureRange: { - min: 25, - max: 35 + min: 0, + max: 0 }, humidityRange: { - min: 40, - max: 70 + min: 0, + max: 0 }, tempSettings: { - min: 25, - max: 35 + min: 0, + max: 0 }, humiditySettings: { - min: 40, - max: 70 + min: 0, + max: 0 }, connectionStatus: { isConnected: false, lastUpdate: null }, - targetTemperature: 30, - targetHumidity: 50, // 空调设定湿度 + targetTemperature: 0, + targetHumidity: 0, // 空调设定湿度 // 温度输入相关 tempInputValue: '', tempValidationMessage: '', @@ -295,36 +312,32 @@ export default { alertHistory: [], // 报警历史记录 // 系统启动事件相关 hasCreatedStartupEvent: false, // 是否已创建启动事件 + acStatus: 4, + // 0待机1启动中2运行中3关机中, 没有就默认连接中 + acStatusList: ['待机', '启动中', '运行中', '关机中', '连接中'], + // 空调控制相关 + acControlLoading: false, // 空调控制按钮加载状态 } }, onLoad() { console.log('环境参数页面加载') - // 从本地存储读取是否已创建启动事件的状态 - const hasCreatedStartupEvent = uni.getStorageSync('hasCreatedStartupEvent') - if (hasCreatedStartupEvent) { - this.hasCreatedStartupEvent = true - console.log('📱 从本地存储读取到启动事件状态: 已创建') - } + // // 从本地存储读取是否已创建启动事件的状态 + // const hasCreatedStartupEvent = uni.getStorageSync('hasCreatedStartupEvent') + // if (hasCreatedStartupEvent) { + // this.hasCreatedStartupEvent = true + // console.log('📱 从本地存储读取到启动事件状态: 已创建') + // } this.initMqttListener(); - this.init(); - // // 获取最新空调温度 - // this.getLatestAirConditionerTemperature() - // // 获取最新温湿度数据 - // this.getLatestWsdData() - // // 首次进入系统时创建启动事件 - // this.createStartupEventIfNeeded() - - // // 获取温湿度区间设置 - // this.loadWsdSettings() }, onShow() { - console.log('📱 环境参数页面显示,触发页面更新') - // 只有在非首次显示时才重新获取最新空调温度 - if (this.hasCreatedStartupEvent) { - this.init(); - } + console.log('📱 环境参数页面显示,触发页面更新'); + this.init(); + // // 只有在非首次显示时才重新获取最新空调温度 + // if (this.hasCreatedStartupEvent) { + // this.init(); + // } }, onUnload() { // 页面卸载时移除监听器 @@ -413,12 +426,12 @@ export default { if (response && response.id) { // 更新温度和湿度控制范围 this.temperatureRange = { - min: response.minTemperature || 25, - max: response.maxTemperature || 35 + min: response.minTemperature || 0, + max: response.maxTemperature || 0 } this.humidityRange = { - min: response.minHumidity || 40, - max: response.maxHumidity || 70 + min: response.minHumidity || 0, + max: response.maxHumidity || 0 } // 同时更新设置弹窗中的临时变量 @@ -433,8 +446,8 @@ export default { } catch (error) { console.error('❌ 温湿度区间设置加载失败:', error) // 使用默认值 - this.temperatureRange = { min: 25, max: 35 } - this.humidityRange = { min: 40, max: 70 } + this.temperatureRange = { min: 0, max: 0 } + this.humidityRange = { min: 0, max: 0 } this.tempSettings = { ...this.temperatureRange } this.humiditySettings = { ...this.humidityRange } console.log('🔄 使用默认温湿度区间设置') @@ -444,20 +457,18 @@ export default { // 获取最新空调温湿度参数 async getLatestAirConditionerTemperature() { try { - console.log('🌡️ 开始获取最新空调温湿度参数...') const res = await thDataApi.getLatest(); if (res.status === 'success') { // 从接口获取温度和湿度设定值 - this.targetTemperature = res.temperature || 30; - this.targetHumidity = res.humidity || 50; + this.targetTemperature = res.temperature || 0; + this.targetHumidity = res.humidity || 0; } - console.log('✅ 最新空调温湿度参数:', res) } catch (error) { console.error('❌ 获取最新空调温湿度参数失败:', error) // 接口失败时使用默认值 - this.targetTemperature = 30; - this.targetHumidity = 50; + this.targetTemperature = 0; + this.targetHumidity = 0; } }, // 获取最新温湿度数据 @@ -465,13 +476,10 @@ export default { try { const res = await wsdApi.getLatest(); if (res.status === 'success') { - // this.temperature = res.wd || 30; - // this.humidity = res.sd || 50 - // this.cleanliness = res.pm || 0; this.updateEnvironmentData({ deviceType: 'WSD', - temperature: res.wd || 30, - humidity: res.sd || 50, + temperature: res.wd || 0, + humidity: res.sd || 0, cleanliness: res.pm || 0, }) } @@ -515,12 +523,8 @@ export default { // 更新环境数据 updateEnvironmentData(data) { - // 处理空调故障状态 - if (data.deviceType === 'AC' && data.faultStatus !== undefined) { - this.acFaultStatus = data.faultStatus - } - - // 只处理WSD设备的数据 + console.log('============data', data) + // 处理WSD设备的数据 if (data.deviceType === 'WSD') { if (data.temperature !== undefined) { this.temperature = parseFloat(data.temperature.toFixed(1)) @@ -531,18 +535,31 @@ export default { this.humidity = parseFloat(data.humidity.toFixed(1)) this.humidityProgress = Math.min(Math.max(this.humidity, 0), 100) } - - if (data.cleanliness !== undefined) { - this.cleanliness = parseFloat(data.cleanliness.toFixed(1)) - this.cleanlinessProgress = Math.min(Math.max(this.cleanliness, 0), 100) - } this.lastUpdate = data.time || new Date().toLocaleString('zh-CN') // 检查报警条件,传入MQTT原始数据 this.checkAlerts(data) + } else if (data.deviceType === 'AC') { + // 处理空调AC数据 + if (data.faultStatus !== undefined) { + this.acFaultStatus = data.faultStatus // 空调故障 + } + if (data.rawData.status !== undefined) { + this.acStatus = data.rawData.status + } + if (data.rawData.ctrlword !== undefined) { + this.acCtrlword = data.rawData.ctrlword + } + } else if (data.deviceType === 'PM25') { + // 处理PM25数据 + if (data.rawData.PM25 !== undefined) { + this.cleanliness = data.rawData.PM25; + this.cleanlinessProgress = Math.min(Math.max(this.cleanliness, 0), 100) + } } else { - console.log('⚠️ 非WSD设备数据,跳过更新:', data.deviceType) + console.log('⚠️ 非WSD、AC、PM25设备数据,跳过更新:', data.deviceType) + // 处理其他设备数据 } }, @@ -566,13 +583,20 @@ export default { if (mqttData.deviceType !== 'WSD') { return } + console.log('====mqttData', mqttData) + // { + // "deviceType": "WSD", + // "temperature": 0, + // "humidity": 0, + // "cleanliness": 0 + // } // 获取MQTT原始数据 const mqttTemperature = mqttData.temperature const mqttHumidity = mqttData.humidity // 1. 温度报警:使用环境控制设置的区间(使用MQTT原始数据) - if (mqttTemperature !== undefined && (mqttTemperature < this.temperatureRange.min || mqttTemperature > this.temperatureRange.max)) { + if (mqttTemperature !== undefined && mqttTemperature !== 0 && (mqttTemperature < this.temperatureRange.min || mqttTemperature > this.temperatureRange.max)) { const alert = { // content: `温度传感器异常,读数${mqttTemperature}°C超出控制范围${this.temperatureRange.min}°C-${this.temperatureRange.max}°C`, content: `温度超出控制范围`, @@ -660,21 +684,21 @@ export default { // 记录报警到控制台并调用创建告警接口 async logAlert(alert) { console.log('🚨 报警触发:', JSON.stringify(alert, null, 2)) - this.alertHistory.push(alert) + // this.alertHistory.push(alert) - // 调用创建告警接口 - try { - console.log('📤 正在创建告警记录...') - const response = await alertApi.create(alert) - console.log('✅ 告警记录创建成功:', response) - } catch (error) { - console.error('❌ 告警记录创建失败:', error) - } + // // 调用创建告警接口 + // try { + // console.log('📤 正在创建告警记录...') + // const response = await alertApi.create(alert) + // console.log('✅ 告警记录创建成功:', response) + // } catch (error) { + // console.error('❌ 告警记录创建失败:', error) + // } - // 限制报警历史记录数量,避免内存溢出 - if (this.alertHistory.length > 100) { - this.alertHistory = this.alertHistory.slice(-50) - } + // // 限制报警历史记录数量,避免内存溢出 + // if (this.alertHistory.length > 100) { + // this.alertHistory = this.alertHistory.slice(-50) + // } }, // 降低目标温度 @@ -875,7 +899,7 @@ export default { this.humidityValidationClass = '' }, - // 显示湿度变化提示 + // 空调湿度更新 showHumidityChangeToast() { // 发送空调参数到MQTT this.sendAirConditionerParams() @@ -889,110 +913,156 @@ export default { this.checkAlerts(mockMqttData) }, - // 显示温度变化提示 - showTemperatureChangeToast() { - // uni.showToast({ - // title: `目标温度: ${this.targetTemperature}°C`, - // icon: 'success', - // duration: 1500 - // }) - - // 发送空调参数到MQTT - this.sendAirConditionerParams() - - // 温度变化后检查报警(使用当前页面数据模拟MQTT数据) - const mockMqttData = { - deviceType: 'WSD', - temperature: this.temperature, - humidity: this.humidity + // 空调温度更新 + showTemperatureChangeToast() { + // 发送空调参数到MQTT + this.sendAirConditionerParams() + + // 温度变化后检查报警(使用当前页面数据模拟MQTT数据) + const mockMqttData = { + deviceType: 'WSD', + temperature: this.temperature, + humidity: this.humidity + } + this.checkAlerts(mockMqttData) + }, + + // 发送空调参数 + async sendAirConditionerParams() { + try { + // 根据MQTT文档,空调温度使用BSQWD,但控制指令可能使用不同的TagName + // 发送温度参数 - 使用BSQWD作为TagName(与接收数据保持一致) + const temperatureData = { + "TagValue": this.targetTemperature, + "TagName": "JS_COD", // 使用与接收数据一致的TagName + "method": "setValue" } - this.checkAlerts(mockMqttData) - }, - // 发送空调参数 - async sendAirConditionerParams() { + // 发送湿度参数 - 根据文档WSD设备湿度使用SD + const humidityData = { + "TagValue": this.targetHumidity, + "TagName": "JS_SD", // 使用与WSD设备湿度一致的TagName + "method": "setValue" + } + + console.log('🌡️ 发送空调温度参数:', temperatureData) + console.log('💧 发送空调湿度参数:', humidityData) + + // 调用发送MQTT数据的方法 + const tempSuccess = sendMqttData(temperatureData) + const humiditySuccess = sendMqttData(humidityData) + + if (tempSuccess && humiditySuccess) { + console.log('✅ 空调温湿度参数MQTT发送请求已提交') + + // 发送成功后调用提交温湿度数据API try { - // 根据MQTT文档,空调温度使用BSQWD,但控制指令可能使用不同的TagName - // 发送温度参数 - 使用BSQWD作为TagName(与接收数据保持一致) - const temperatureData = { - "TagValue": this.targetTemperature, - "TagName": "JS_COD", // 使用与接收数据一致的TagName - "method": "setValue" - } - - // 发送湿度参数 - 根据文档WSD设备湿度使用SD - const humidityData = { - "TagValue": this.targetHumidity, - "TagName": "JS_SD", // 使用与WSD设备湿度一致的TagName - "method": "setValue" - } - - console.log('🌡️ 发送空调温度参数:', temperatureData) - console.log('💧 发送空调湿度参数:', humidityData) - - // 调用发送MQTT数据的方法 - const tempSuccess = sendMqttData(temperatureData) - const humiditySuccess = sendMqttData(humidityData) - - if (tempSuccess && humiditySuccess) { - console.log('✅ 空调温湿度参数MQTT发送请求已提交') - - // 发送成功后调用提交温湿度数据API - try { - await this.submitTemperatureData() - // 显示成功提示 - uni.showToast({ - title: '参数设置成功', - icon: 'success', - duration: 1500 - }) - } catch (apiError) { - // MQTT发送成功,但接口保存失败 - console.warn('⚠️ MQTT发送成功,但接口保存失败:', apiError) - uni.showToast({ - title: 'MQTT已发送,接口保存失败', - icon: 'none', - duration: 2500 - }) - } - - } else { - console.error('❌ 空调参数MQTT发送失败') - uni.showToast({ - title: 'MQTT发送失败', - icon: 'error', - duration: 2000 - }) - } - } catch (error) { - console.error('❌ 发送空调参数异常:', error) + await this.submitTemperatureData() + // 显示成功提示 uni.showToast({ - title: '参数设置失败', - icon: 'error', - duration: 2000 + title: '参数设置成功', + icon: 'success', + duration: 1500 + }) + } catch (apiError) { + // MQTT发送成功,但接口保存失败 + console.warn('⚠️ MQTT发送成功,但接口保存失败:', apiError) + uni.showToast({ + title: 'MQTT已发送,接口保存失败', + icon: 'none', + duration: 2500 }) } - }, + + } else { + console.error('❌ 空调参数MQTT发送失败') + uni.showToast({ + title: 'MQTT发送失败', + icon: 'error', + duration: 2000 + }) + } + } catch (error) { + console.error('❌ 发送空调参数异常:', error) + uni.showToast({ + title: '参数设置失败', + icon: 'error', + duration: 2000 + }) + } + }, + + // 空调开机控制 + async turnOnAirConditioner() { + await this.sendAirConditionerControl(1, '开机') + }, + + // 空调关机控制 + async turnOffAirConditioner() { + await this.sendAirConditionerControl(2, '关机') + }, + + // 发送空调控制指令 + async sendAirConditionerControl(ctrlValue, actionName) { + this.acControlLoading = true; + try { + // 构建控制指令数据 + const controlData = { + "TagValue": ctrlValue, + "TagName": "ctrlword", + "method": "setValue" + } - // 提交温湿度数据 - async submitTemperatureData() { - try { - const temperatureHumidityData = { - temperature: this.targetTemperature, - humidity: this.targetHumidity, - deviceId: "TH_SENSOR_001", - timestamp: new Date().toISOString(), - source: "manual_setting" // 标识为手动设置 - } - console.log('📤 提交温湿度数据到接口:', temperatureHumidityData) - const response = await thDataApi.submit(temperatureHumidityData) - console.log('✅ 温湿度数据接口提交成功:', response) - } catch (error) { - console.error('❌ 温湿度数据接口提交失败:', error) - // 接口失败不影响MQTT发送,只记录日志 - throw error // 重新抛出错误,让调用方知道接口失败了 - } - }, + console.log(`🔧 发送空调${actionName}指令:`, controlData) + + // 调用发送MQTT数据的方法 + const success = sendMqttData(controlData) + this.acControlLoading = false; + if (success) { + // 显示成功提示 + uni.showToast({ + title: `${actionName}指令已发送`, + icon: 'success', + duration: 1500 + }) + } else { + uni.showToast({ + title: `${actionName}失败`, + icon: 'error', + duration: 2000 + }) + } + } catch (error) { + uni.showToast({ + title: `${actionName}异常`, + icon: 'error', + duration: 2000 + }) + } finally { + // 清除加载状态 + this.acControlLoading = false + } + }, + + // 提交温湿度数据 + async submitTemperatureData() { + try { + const temperatureHumidityData = { + temperature: this.targetTemperature, + humidity: this.targetHumidity, + deviceId: "TH_SENSOR_001", + timestamp: new Date().toISOString(), + source: "manual_setting" // 标识为手动设置 + } + console.log('📤 提交温湿度数据到接口:', temperatureHumidityData) + const response = await thDataApi.submit(temperatureHumidityData) + console.log('✅ 温湿度数据接口提交成功:', response) + } catch (error) { + console.error('❌ 温湿度数据接口提交失败:', error) + // 接口失败不影响MQTT发送,只记录日志 + throw error // 重新抛出错误,让调用方知道接口失败了 + } + }, // 手动重连MQTT manualReconnect() { @@ -1449,6 +1519,58 @@ export default { box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2); } +/* 空调开关控制按钮样式 */ +.ac-power-controls { + display: flex; + justify-content: center; + gap: 20rpx; + background: #ffffff; + border-radius: 8rpx; + padding: 20rpx; + border: 1rpx solid #e1e5e9; + margin-bottom: 12rpx; +} + +.ac-power-btn { + flex: 1; + max-width: 120rpx; + // height: 60rpx; + border-radius: 6rpx; + border: 1rpx solid; + font-size: 28rpx; + font-weight: 500; + transition: all 0.2s ease; +} + +.power-on { + background: #27ae60; + color: white; + border-color: #27ae60; +} + +.power-on:active { + background: #229954; + border-color: #229954; +} + +.power-off { + background: #e74c3c; + color: white; + border-color: #e74c3c; +} + +.power-off:active { + background: #c0392b; + border-color: #c0392b; +} + +.ac-power-btn:disabled { + background: #bdc3c7; + color: #7f8c8d; + border-color: #bdc3c7; + opacity: 0.6; +} + .ac-temp-display { display: flex; align-items: baseline; diff --git a/src/pages/visual/index.vue b/src/pages/visual/index.vue index 1a5a4a6..f3ed819 100644 --- a/src/pages/visual/index.vue +++ b/src/pages/visual/index.vue @@ -25,31 +25,6 @@ - - - - 📡 设备状态 - 在线 - - - 🎥 分辨率 - 高清 - - - 🔊 音频 - 开启 - - - - - - - - @@ -136,55 +111,6 @@ export default { }) }, - // 初始化播放器 - async handleInitPlayer() { - console.log('🔄 重新初始化播放器...') - - uni.showLoading({ - title: '正在初始化...' - }) - - try { - // 重新获取视频数据并初始化 - await this.getVideoData() - - uni.hideLoading() - uni.showToast({ - title: '初始化成功', - icon: 'success', - duration: 2000 - }) - - // 重置播放状态 - this.isPlaying = true - - } catch (error) { - uni.hideLoading() - console.error('初始化失败:', error) - uni.showToast({ - title: '初始化失败', - icon: 'error', - duration: 2000 - }) - } - }, - - // 切换播放/暂停 - handleTogglePlay() { - console.log('🎬 切换播放状态:', this.isPlaying ? '暂停' : '播放') - - if (this.$refs.playerVideoRef) { - // 调用播放器组件的切换播放方法(状态会通过事件同步) - this.$refs.playerVideoRef.togglePlay() - } else { - console.error('❌ 播放器组件未找到') - uni.showToast({ - title: '播放器未就绪', - icon: 'error', - duration: 2000 - }) - } - }, // 处理播放状态变化(由播放器组件触发) handlePlayStateChange(isPlaying) { diff --git a/src/utils/mqttDataManager.js b/src/utils/mqttDataManager.js index 9ba69fb..d6dbe70 100644 --- a/src/utils/mqttDataManager.js +++ b/src/utils/mqttDataManager.js @@ -9,7 +9,14 @@ class MqttDataManager { temperature: null, humidity: null, pm25: null, - timestamp: null + timestamp: null, + // 扩展数据结构支持更多设备类型 + deviceType: null, + rawData: null, + // 空调设备数据 + acTemperature: null, + // 其他设备数据可在此扩展 + otherDeviceData: {} } this.init() } @@ -95,41 +102,65 @@ class MqttDataManager { // console.log('设备数据:', deviceDataContent) // console.log('时间戳:', timestamp) - // 根据设备类型处理数据 - if (deviceType === 'WSD') { - // console.log('✅ 处理WSD设备数据 - 更新环境参数') - this.processWSDData(deviceDataContent, timestamp) - } else { - // console.log(`⚠️ 设备类型 ${deviceType} 暂不处理,仅打印到控制台`) - // console.log('设备详情:', { - // deviceType, - // data: deviceDataContent, - // timestamp: new Date(timestamp * 1000).toLocaleString('zh-CN') - // }) - } + // 处理所有设备类型的数据 + console.log(`✅ 处理设备类型: ${deviceType}`) + this.processAllDeviceData(deviceType, deviceDataContent, timestamp) } catch (error) { console.error('❌ 处理设备数据失败:', error) } } - // 处理WSD设备数据 - processWSDData(data, timestamp) { + // 处理所有设备类型的数据 + processAllDeviceData(deviceType, data, timestamp) { try { - // 解析WSD数据 - 根据您提供的数据结构,WD是温度,SD是湿度 - const temperature = data.WD && parseFloat(data.WD); - const humidity = data.SD && parseFloat(data.SD); + console.log(`🌡️ ${deviceType}设备数据解析:`) + console.log('设备数据:', data) - // console.log('🌡️ WSD数据解析:') - // console.log('温度(WD):', temperature) - // console.log('湿度(SD):', humidity) - - // 构建解析后的数据 + // 构建基础解析数据 const parsedData = { - deviceType: 'WSD', + deviceType, timestamp, time: new Date(timestamp * 1000).toLocaleString('zh-CN'), - temperature, - humidity + rawData: data // 保存原始数据供页面特殊处理 + } + + // 根据设备类型解析特定数据 + switch (deviceType) { + case 'WSD': // 温湿度传感器 + if (data.WD !== undefined) { + parsedData.temperature = parseFloat(data.WD) + } + if (data.SD !== undefined) { + parsedData.humidity = parseFloat(data.SD) + } + break + + case 'AC': // 空调设备 + if (data.BSQWD !== undefined) { + parsedData.acTemperature = parseFloat(data.BSQWD) + } + + if (data.SD !== undefined) { + parsedData.acHumidity = parseFloat(data.SD) + } + + if (data.status !== undefined) { + parsedData.acStatus = data.status + } + // 可以添加其他空调参数 + break + + case 'PM': // PM2.5传感器 + if (data.PM25 !== undefined) { + parsedData.pm25 = parseFloat(data.PM25) + } + break + + default: + // 其他设备类型,保存到otherDeviceData中 + console.log(`📦 处理未知设备类型 ${deviceType},保存原始数据`) + // parsedData.otherDeviceData = data + break } // 更新最新数据 @@ -138,9 +169,9 @@ class MqttDataManager { // 通知所有监听器 this.notifyListeners('dataUpdate', parsedData) - // console.log('✅ WSD数据处理完成:', parsedData) + console.log(`✅ ${deviceType}数据处理完成:`, parsedData) } catch (error) { - console.error('❌ 处理WSD数据失败:', error) + console.error(`❌ 处理${deviceType}数据失败:`, error) } } @@ -198,16 +229,18 @@ class MqttDataManager { // 更新最新数据 updateLastData(parsedData) { - if (parsedData.temperature !== undefined) { - this.lastData.temperature = parsedData.temperature - } - if (parsedData.humidity !== undefined) { - this.lastData.humidity = parsedData.humidity - } - if (parsedData.pm25 !== undefined) { - this.lastData.pm25 = parsedData.pm25 - } - this.lastData.timestamp = parsedData.timestamp + // 直接合并数据,只更新有值的字段 + Object.keys(parsedData).forEach(key => { + if (parsedData[key] !== undefined) { + if (key === 'otherDeviceData' && this.lastData.otherDeviceData) { + // 对于 otherDeviceData,进行深度合并 + this.lastData.otherDeviceData = { ...this.lastData.otherDeviceData, ...parsedData.otherDeviceData } + } else { + // 其他字段直接赋值 + this.lastData[key] = parsedData[key] + } + } + }) } // 添加数据监听器 diff --git a/萤石云APP对接完整指南.md b/萤石云APP对接完整指南.md index dc3a5bb..074af86 100644 --- a/萤石云APP对接完整指南.md +++ b/萤石云APP对接完整指南.md @@ -271,591 +271,21 @@ src/ ### 1. 播放器组件 (`EzvizVideoPlayerSimple.vue`) -```vue - - - -``` - ---- - ### 2. iframe HTML (`ezviz-iframe.html`) -```html - - - - - - 萤石云播放器 - - - -
正在加载播放器...
- - - - - -``` - ---- - ### 3. 监控页面 (`pages/visual/index.vue`) -```vue - - - - - -``` - ---- - ### 4. AccessToken 管理 (`utils/ezvizTokenManager.js`) -```javascript -// 萤石云 AccessToken 管理器 -class EzvizTokenManager { - constructor() { - this.appKey = 'your-app-key' - this.appSecret = 'your-app-secret' - this.baseUrl = 'https://open.ys7.com/api/lapp' - } - - // 获取有效的 AccessToken - async getValidAccessToken() { - // 1. 先从缓存读取 - const cached = uni.getStorageSync('ezviz_access_token') - const expireTime = uni.getStorageSync('ezviz_token_expire') - - // 2. 检查是否过期(提前1小时刷新) - const now = Date.now() - if (cached && expireTime && expireTime - now > 3600000) { - console.log('使用缓存的AccessToken') - return cached - } - - // 3. 缓存失效,重新获取 - console.log('重新获取AccessToken') - return await this.fetchAccessToken() - } - - // 从萤石云服务器获取 AccessToken - async fetchAccessToken() { - return new Promise((resolve, reject) => { - uni.request({ - url: `${this.baseUrl}/token/get`, - method: 'POST', - header: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - data: { - appKey: this.appKey, - appSecret: this.appSecret - }, - success: (res) => { - if (res.data.code === '200') { - const accessToken = res.data.data.accessToken - const expireTime = Date.now() + (res.data.data.expireTime * 1000) - - // 缓存 token - uni.setStorageSync('ezviz_access_token', accessToken) - uni.setStorageSync('ezviz_token_expire', expireTime) - - console.log('✅ AccessToken获取成功') - resolve(accessToken) - } else { - reject(new Error(res.data.msg || '获取AccessToken失败')) - } - }, - fail: (error) => { - reject(error) - } - }) - }) - } -} - -export default new EzvizTokenManager() -``` - ---- - ## ⚙️ 配置说明 ### 1. pages.json(横屏配置) -```json -{ - "pages": [ - { - "path": "pages/visual/index", - "style": { - "navigationBarTitleText": "移动式检修车间", - "navigationStyle": "custom", - "pageOrientation": "landscape" // ← 横屏展示 - } - } - ] -} -``` - ---- - ### 2. manifest.json(内存配置) -```json -{ - "app-plus": { - "compatible": { - "largeHeap": true // ← 启用大内存堆(512MB) - } - } -} -``` - ---- - ### 3. 萤石云参数说明 #### AccessToken 获取 -```javascript -// API: https://open.ys7.com/api/lapp/token/get -// 方法: POST -// 参数: -{ - appKey: "your-app-key", - appSecret: "your-app-secret" -} - -// 返回: -{ - code: "200", - data: { - accessToken: "at.xxx...", - expireTime: 7200 // 秒,默认2小时 - } -} -``` - #### ezopen 播放地址格式 ``` ezopen://open.ys7.com/{设备序列号}/{通道号}.{清晰度}.live @@ -1223,3 +653,5 @@ onHide() { 🎉 **恭喜!萤石云APP对接完成!** 🎉 + +