feat:空调开关、状态
This commit is contained in:
@ -248,3 +248,5 @@ console.log('组件ref:', this.$refs.playerVideoRef)
|
|||||||
|
|
||||||
需要详细说明?查看 → [萤石云APP对接完整指南.md](./萤石云APP对接完整指南.md)
|
需要详细说明?查看 → [萤石云APP对接完整指南.md](./萤石云APP对接完整指南.md)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -17,13 +17,33 @@
|
|||||||
></web-view>
|
></web-view>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
|
|
||||||
<!-- H5平台提示 -->
|
<!-- H5平台直接使用iframe -->
|
||||||
<!-- #ifdef H5 -->
|
<!-- #ifdef H5 -->
|
||||||
<view class="h5-tip">H5平台暂不支持</view>
|
<view v-if="iframeUrl" class="h5-iframe-container">
|
||||||
|
<iframe
|
||||||
|
:src="iframeUrl"
|
||||||
|
class="h5-iframe"
|
||||||
|
allow="autoplay; fullscreen"
|
||||||
|
allowfullscreen
|
||||||
|
></iframe>
|
||||||
|
</view>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
|
|
||||||
<!-- 控制按钮 -->
|
<!-- APP平台:使用 cover-view -->
|
||||||
<view class="control-buttons" v-if="!loading && !error">
|
<!-- #ifdef APP-PLUS -->
|
||||||
|
<cover-view class="control-buttons-cover" v-if="!loading && webviewUrl">
|
||||||
|
<cover-view class="control-btn-cover play-btn-cover" @click="togglePlay">
|
||||||
|
<cover-view class="btn-text">{{ isPlaying ? '⏸ 暂停' : '▶ 播放' }}</cover-view>
|
||||||
|
</cover-view>
|
||||||
|
<cover-view class="control-btn-cover refresh-btn-cover" @click="refresh">
|
||||||
|
<cover-view class="btn-text">🔄 刷新</cover-view>
|
||||||
|
</cover-view>
|
||||||
|
</cover-view>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<!-- H5平台:使用普通按钮 -->
|
||||||
|
<!-- #ifdef H5 -->
|
||||||
|
<view class="control-buttons" v-if="!loading && iframeUrl">
|
||||||
<button class="control-btn play-btn" @click="togglePlay">
|
<button class="control-btn play-btn" @click="togglePlay">
|
||||||
{{ isPlaying ? '⏸ 暂停' : '▶ 播放' }}
|
{{ isPlaying ? '⏸ 暂停' : '▶ 播放' }}
|
||||||
</button>
|
</button>
|
||||||
@ -31,6 +51,7 @@
|
|||||||
🔄 刷新
|
🔄 刷新
|
||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
<view v-if="loading" class="loading">
|
<view v-if="loading" class="loading">
|
||||||
<text>{{ loadingText }}</text>
|
<text>{{ loadingText }}</text>
|
||||||
@ -39,6 +60,16 @@
|
|||||||
<view v-if="error" class="error">
|
<view v-if="error" class="error">
|
||||||
<text>{{ errorText }}</text>
|
<text>{{ errorText }}</text>
|
||||||
<button @click="retry">重试</button>
|
<button @click="retry">重试</button>
|
||||||
|
|
||||||
|
<!-- 即使有错误也显示控制按钮 -->
|
||||||
|
<view class="error-controls">
|
||||||
|
<button class="control-btn play-btn" @click="togglePlay">
|
||||||
|
{{ isPlaying ? '⏸ 暂停' : '▶ 播放' }}
|
||||||
|
</button>
|
||||||
|
<button class="control-btn refresh-btn" @click="refresh">
|
||||||
|
🔄 刷新
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@ -56,7 +87,8 @@ export default {
|
|||||||
return {
|
return {
|
||||||
platform: '',
|
platform: '',
|
||||||
status: '未初始化',
|
status: '未初始化',
|
||||||
webviewUrl: '',
|
webviewUrl: '', // APP平台使用
|
||||||
|
iframeUrl: '', // H5平台使用
|
||||||
loading: false,
|
loading: false,
|
||||||
loadingText: '',
|
loadingText: '',
|
||||||
error: false,
|
error: false,
|
||||||
@ -96,12 +128,26 @@ export default {
|
|||||||
this.status = '加载中'
|
this.status = '加载中'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
// APP平台:使用本地HTML文件
|
||||||
const token = encodeURIComponent(config.accessToken)
|
const token = encodeURIComponent(config.accessToken)
|
||||||
const url = encodeURIComponent(config.play_url)
|
const url = encodeURIComponent(config.play_url)
|
||||||
// 使用iframe版本(内存占用更小)
|
|
||||||
this.webviewUrl = `/static/html/ezviz-iframe.html?accessToken=${token}&playUrl=${url}`
|
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(() => {
|
setTimeout(() => {
|
||||||
if (this.loading) {
|
if (this.loading) {
|
||||||
@ -154,7 +200,13 @@ export default {
|
|||||||
// 因为iframe播放器不支持直接控制,所以采用重新加载的方式
|
// 因为iframe播放器不支持直接控制,所以采用重新加载的方式
|
||||||
if (this.isPlaying) {
|
if (this.isPlaying) {
|
||||||
// 暂停:清空URL
|
// 暂停:清空URL
|
||||||
|
// #ifdef APP-PLUS
|
||||||
this.webviewUrl = ''
|
this.webviewUrl = ''
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
this.iframeUrl = ''
|
||||||
|
// #endif
|
||||||
|
|
||||||
this.isPlaying = false
|
this.isPlaying = false
|
||||||
this.status = '已暂停'
|
this.status = '已暂停'
|
||||||
|
|
||||||
@ -163,9 +215,23 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
// 播放:重新设置URL
|
// 播放:重新设置URL
|
||||||
if (this.config) {
|
if (this.config) {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
const token = encodeURIComponent(this.config.accessToken)
|
const token = encodeURIComponent(this.config.accessToken)
|
||||||
const url = encodeURIComponent(this.config.play_url)
|
const url = encodeURIComponent(this.config.play_url)
|
||||||
this.webviewUrl = `/static/html/ezviz-iframe.html?accessToken=${token}&playUrl=${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.isPlaying = true
|
||||||
this.status = '播放中'
|
this.status = '播放中'
|
||||||
|
|
||||||
@ -195,12 +261,31 @@ export default {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 先清空再重新加载
|
// 先清空再重新加载
|
||||||
|
// #ifdef APP-PLUS
|
||||||
this.webviewUrl = ''
|
this.webviewUrl = ''
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
this.iframeUrl = ''
|
||||||
|
// #endif
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
const token = encodeURIComponent(this.config.accessToken)
|
const token = encodeURIComponent(this.config.accessToken)
|
||||||
const url = encodeURIComponent(this.config.play_url)
|
const url = encodeURIComponent(this.config.play_url)
|
||||||
this.webviewUrl = `/static/html/ezviz-iframe.html?accessToken=${token}&playUrl=${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.isPlaying = true
|
||||||
this.status = '播放中'
|
this.status = '播放中'
|
||||||
|
|
||||||
@ -244,16 +329,27 @@ export default {
|
|||||||
|
|
||||||
.video-webview {
|
.video-webview {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 50%;
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h5-tip {
|
/* H5平台iframe容器 */
|
||||||
display: flex;
|
.h5-iframe-container {
|
||||||
align-items: center;
|
width: 100%;
|
||||||
justify-content: center;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
color: white;
|
position: absolute;
|
||||||
font-size: 28rpx;
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
@ -300,6 +396,36 @@ export default {
|
|||||||
border-radius: 10rpx;
|
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 {
|
.control-buttons {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@ -24,9 +24,9 @@
|
|||||||
<view class="detail-progress-bar">
|
<view class="detail-progress-bar">
|
||||||
<view class="detail-progress-fill temperature-progress" :style="{ width: temperatureProgress + '%' }"></view>
|
<view class="detail-progress-fill temperature-progress" :style="{ width: temperatureProgress + '%' }"></view>
|
||||||
</view>
|
</view>
|
||||||
<view class="detail-range">
|
<!-- <view class="detail-range">
|
||||||
<text class="detail-range-text">{{ 0 }}°C - {{ 100 }}°C</text>
|
<text class="detail-range-text">{{ 0 }}°C - {{ 100 }}°C</text>
|
||||||
</view>
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 湿度卡片 -->
|
<!-- 湿度卡片 -->
|
||||||
@ -44,9 +44,9 @@
|
|||||||
<view class="detail-progress-bar">
|
<view class="detail-progress-bar">
|
||||||
<view class="detail-progress-fill humidity-progress" :style="{ width: humidityProgress + '%' }"></view>
|
<view class="detail-progress-fill humidity-progress" :style="{ width: humidityProgress + '%' }"></view>
|
||||||
</view>
|
</view>
|
||||||
<view class="detail-range">
|
<!-- <view class="detail-range">
|
||||||
<text class="detail-range-text">{{ 0 }}% - {{ 100 }}%</text>
|
<text class="detail-range-text">{{ 0 }}% - {{ 100 }}%</text>
|
||||||
</view>
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 洁净度卡片 -->
|
<!-- 洁净度卡片 -->
|
||||||
@ -64,22 +64,39 @@
|
|||||||
<view class="detail-progress-bar">
|
<view class="detail-progress-bar">
|
||||||
<view class="detail-progress-fill cleanliness-progress" :style="{ width: cleanlinessProgress + '%' }"></view>
|
<view class="detail-progress-fill cleanliness-progress" :style="{ width: cleanlinessProgress + '%' }"></view>
|
||||||
</view>
|
</view>
|
||||||
<view class="detail-range">
|
<!-- <view class="detail-range">
|
||||||
<text class="detail-range-text">{{ 0 }}% - {{ 100 }}%</text>
|
<text class="detail-range-text">{{ 0 }}% - {{ 100 }}%</text>
|
||||||
</view>
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 空调目标参数设置 -->
|
<!-- 空调设置 -->
|
||||||
<view class="air-conditioner-settings">
|
<view class="air-conditioner-settings">
|
||||||
<view class="ac-header">
|
<view class="ac-header">
|
||||||
<view class="ac-title-container">
|
<view class="ac-title-container">
|
||||||
<view class="ac-icon">❄️</view>
|
<view class="ac-icon">❄️</view>
|
||||||
<text class="ac-title">空调目标参数设置</text>
|
<text class="ac-title">空调设置</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="ac-status-indicator">
|
<view class="ac-status-indicator">
|
||||||
<view class="status-dot active"></view>
|
<view class="status-dot active"></view>
|
||||||
<text class="status-label">运行中</text>
|
<text class="status-label">{{ acStatusList[acStatus] }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 空调开关控制卡片 -->
|
||||||
|
<view class="ac-control-card power-card">
|
||||||
|
<view class="control-header">
|
||||||
|
<view class="control-icon-container">
|
||||||
|
<text class="control-label">设备控制</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="ac-power-controls">
|
||||||
|
<button class="ac-power-btn power-on" @click="turnOnAirConditioner" :disabled="acControlLoading">
|
||||||
|
开机
|
||||||
|
</button>
|
||||||
|
<button class="ac-power-btn power-off" @click="turnOffAirConditioner" :disabled="acControlLoading">
|
||||||
|
关机
|
||||||
|
</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -259,27 +276,27 @@ export default {
|
|||||||
cleanlinessProgress: 0,
|
cleanlinessProgress: 0,
|
||||||
lastUpdate: '暂无数据',
|
lastUpdate: '暂无数据',
|
||||||
temperatureRange: {
|
temperatureRange: {
|
||||||
min: 25,
|
min: 0,
|
||||||
max: 35
|
max: 0
|
||||||
},
|
},
|
||||||
humidityRange: {
|
humidityRange: {
|
||||||
min: 40,
|
min: 0,
|
||||||
max: 70
|
max: 0
|
||||||
},
|
},
|
||||||
tempSettings: {
|
tempSettings: {
|
||||||
min: 25,
|
min: 0,
|
||||||
max: 35
|
max: 0
|
||||||
},
|
},
|
||||||
humiditySettings: {
|
humiditySettings: {
|
||||||
min: 40,
|
min: 0,
|
||||||
max: 70
|
max: 0
|
||||||
},
|
},
|
||||||
connectionStatus: {
|
connectionStatus: {
|
||||||
isConnected: false,
|
isConnected: false,
|
||||||
lastUpdate: null
|
lastUpdate: null
|
||||||
},
|
},
|
||||||
targetTemperature: 30,
|
targetTemperature: 0,
|
||||||
targetHumidity: 50, // 空调设定湿度
|
targetHumidity: 0, // 空调设定湿度
|
||||||
// 温度输入相关
|
// 温度输入相关
|
||||||
tempInputValue: '',
|
tempInputValue: '',
|
||||||
tempValidationMessage: '',
|
tempValidationMessage: '',
|
||||||
@ -295,36 +312,32 @@ export default {
|
|||||||
alertHistory: [], // 报警历史记录
|
alertHistory: [], // 报警历史记录
|
||||||
// 系统启动事件相关
|
// 系统启动事件相关
|
||||||
hasCreatedStartupEvent: false, // 是否已创建启动事件
|
hasCreatedStartupEvent: false, // 是否已创建启动事件
|
||||||
|
acStatus: 4,
|
||||||
|
// 0待机1启动中2运行中3关机中, 没有就默认连接中
|
||||||
|
acStatusList: ['待机', '启动中', '运行中', '关机中', '连接中'],
|
||||||
|
// 空调控制相关
|
||||||
|
acControlLoading: false, // 空调控制按钮加载状态
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad() {
|
onLoad() {
|
||||||
console.log('环境参数页面加载')
|
console.log('环境参数页面加载')
|
||||||
|
|
||||||
// 从本地存储读取是否已创建启动事件的状态
|
// // 从本地存储读取是否已创建启动事件的状态
|
||||||
const hasCreatedStartupEvent = uni.getStorageSync('hasCreatedStartupEvent')
|
// const hasCreatedStartupEvent = uni.getStorageSync('hasCreatedStartupEvent')
|
||||||
if (hasCreatedStartupEvent) {
|
// if (hasCreatedStartupEvent) {
|
||||||
this.hasCreatedStartupEvent = true
|
// this.hasCreatedStartupEvent = true
|
||||||
console.log('📱 从本地存储读取到启动事件状态: 已创建')
|
// console.log('📱 从本地存储读取到启动事件状态: 已创建')
|
||||||
}
|
// }
|
||||||
|
|
||||||
this.initMqttListener();
|
this.initMqttListener();
|
||||||
this.init();
|
|
||||||
// // 获取最新空调温度
|
|
||||||
// this.getLatestAirConditionerTemperature()
|
|
||||||
// // 获取最新温湿度数据
|
|
||||||
// this.getLatestWsdData()
|
|
||||||
// // 首次进入系统时创建启动事件
|
|
||||||
// this.createStartupEventIfNeeded()
|
|
||||||
|
|
||||||
// // 获取温湿度区间设置
|
|
||||||
// this.loadWsdSettings()
|
|
||||||
},
|
},
|
||||||
onShow() {
|
onShow() {
|
||||||
console.log('📱 环境参数页面显示,触发页面更新')
|
console.log('📱 环境参数页面显示,触发页面更新');
|
||||||
// 只有在非首次显示时才重新获取最新空调温度
|
this.init();
|
||||||
if (this.hasCreatedStartupEvent) {
|
// // 只有在非首次显示时才重新获取最新空调温度
|
||||||
this.init();
|
// if (this.hasCreatedStartupEvent) {
|
||||||
}
|
// this.init();
|
||||||
|
// }
|
||||||
},
|
},
|
||||||
onUnload() {
|
onUnload() {
|
||||||
// 页面卸载时移除监听器
|
// 页面卸载时移除监听器
|
||||||
@ -413,12 +426,12 @@ export default {
|
|||||||
if (response && response.id) {
|
if (response && response.id) {
|
||||||
// 更新温度和湿度控制范围
|
// 更新温度和湿度控制范围
|
||||||
this.temperatureRange = {
|
this.temperatureRange = {
|
||||||
min: response.minTemperature || 25,
|
min: response.minTemperature || 0,
|
||||||
max: response.maxTemperature || 35
|
max: response.maxTemperature || 0
|
||||||
}
|
}
|
||||||
this.humidityRange = {
|
this.humidityRange = {
|
||||||
min: response.minHumidity || 40,
|
min: response.minHumidity || 0,
|
||||||
max: response.maxHumidity || 70
|
max: response.maxHumidity || 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// 同时更新设置弹窗中的临时变量
|
// 同时更新设置弹窗中的临时变量
|
||||||
@ -433,8 +446,8 @@ export default {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 温湿度区间设置加载失败:', error)
|
console.error('❌ 温湿度区间设置加载失败:', error)
|
||||||
// 使用默认值
|
// 使用默认值
|
||||||
this.temperatureRange = { min: 25, max: 35 }
|
this.temperatureRange = { min: 0, max: 0 }
|
||||||
this.humidityRange = { min: 40, max: 70 }
|
this.humidityRange = { min: 0, max: 0 }
|
||||||
this.tempSettings = { ...this.temperatureRange }
|
this.tempSettings = { ...this.temperatureRange }
|
||||||
this.humiditySettings = { ...this.humidityRange }
|
this.humiditySettings = { ...this.humidityRange }
|
||||||
console.log('🔄 使用默认温湿度区间设置')
|
console.log('🔄 使用默认温湿度区间设置')
|
||||||
@ -444,20 +457,18 @@ export default {
|
|||||||
// 获取最新空调温湿度参数
|
// 获取最新空调温湿度参数
|
||||||
async getLatestAirConditionerTemperature() {
|
async getLatestAirConditionerTemperature() {
|
||||||
try {
|
try {
|
||||||
console.log('🌡️ 开始获取最新空调温湿度参数...')
|
|
||||||
const res = await thDataApi.getLatest();
|
const res = await thDataApi.getLatest();
|
||||||
if (res.status === 'success') {
|
if (res.status === 'success') {
|
||||||
// 从接口获取温度和湿度设定值
|
// 从接口获取温度和湿度设定值
|
||||||
this.targetTemperature = res.temperature || 30;
|
this.targetTemperature = res.temperature || 0;
|
||||||
this.targetHumidity = res.humidity || 50;
|
this.targetHumidity = res.humidity || 0;
|
||||||
}
|
}
|
||||||
console.log('✅ 最新空调温湿度参数:', res)
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 获取最新空调温湿度参数失败:', error)
|
console.error('❌ 获取最新空调温湿度参数失败:', error)
|
||||||
// 接口失败时使用默认值
|
// 接口失败时使用默认值
|
||||||
this.targetTemperature = 30;
|
this.targetTemperature = 0;
|
||||||
this.targetHumidity = 50;
|
this.targetHumidity = 0;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 获取最新温湿度数据
|
// 获取最新温湿度数据
|
||||||
@ -465,13 +476,10 @@ export default {
|
|||||||
try {
|
try {
|
||||||
const res = await wsdApi.getLatest();
|
const res = await wsdApi.getLatest();
|
||||||
if (res.status === 'success') {
|
if (res.status === 'success') {
|
||||||
// this.temperature = res.wd || 30;
|
|
||||||
// this.humidity = res.sd || 50
|
|
||||||
// this.cleanliness = res.pm || 0;
|
|
||||||
this.updateEnvironmentData({
|
this.updateEnvironmentData({
|
||||||
deviceType: 'WSD',
|
deviceType: 'WSD',
|
||||||
temperature: res.wd || 30,
|
temperature: res.wd || 0,
|
||||||
humidity: res.sd || 50,
|
humidity: res.sd || 0,
|
||||||
cleanliness: res.pm || 0,
|
cleanliness: res.pm || 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -515,12 +523,8 @@ export default {
|
|||||||
|
|
||||||
// 更新环境数据
|
// 更新环境数据
|
||||||
updateEnvironmentData(data) {
|
updateEnvironmentData(data) {
|
||||||
// 处理空调故障状态
|
console.log('============data', data)
|
||||||
if (data.deviceType === 'AC' && data.faultStatus !== undefined) {
|
// 处理WSD设备的数据
|
||||||
this.acFaultStatus = data.faultStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只处理WSD设备的数据
|
|
||||||
if (data.deviceType === 'WSD') {
|
if (data.deviceType === 'WSD') {
|
||||||
if (data.temperature !== undefined) {
|
if (data.temperature !== undefined) {
|
||||||
this.temperature = parseFloat(data.temperature.toFixed(1))
|
this.temperature = parseFloat(data.temperature.toFixed(1))
|
||||||
@ -532,17 +536,30 @@ export default {
|
|||||||
this.humidityProgress = Math.min(Math.max(this.humidity, 0), 100)
|
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')
|
this.lastUpdate = data.time || new Date().toLocaleString('zh-CN')
|
||||||
|
|
||||||
// 检查报警条件,传入MQTT原始数据
|
// 检查报警条件,传入MQTT原始数据
|
||||||
this.checkAlerts(data)
|
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 {
|
} else {
|
||||||
console.log('⚠️ 非WSD设备数据,跳过更新:', data.deviceType)
|
console.log('⚠️ 非WSD、AC、PM25设备数据,跳过更新:', data.deviceType)
|
||||||
|
// 处理其他设备数据
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -566,13 +583,20 @@ export default {
|
|||||||
if (mqttData.deviceType !== 'WSD') {
|
if (mqttData.deviceType !== 'WSD') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
console.log('====mqttData', mqttData)
|
||||||
|
// {
|
||||||
|
// "deviceType": "WSD",
|
||||||
|
// "temperature": 0,
|
||||||
|
// "humidity": 0,
|
||||||
|
// "cleanliness": 0
|
||||||
|
// }
|
||||||
|
|
||||||
// 获取MQTT原始数据
|
// 获取MQTT原始数据
|
||||||
const mqttTemperature = mqttData.temperature
|
const mqttTemperature = mqttData.temperature
|
||||||
const mqttHumidity = mqttData.humidity
|
const mqttHumidity = mqttData.humidity
|
||||||
|
|
||||||
// 1. 温度报警:使用环境控制设置的区间(使用MQTT原始数据)
|
// 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 = {
|
const alert = {
|
||||||
// content: `温度传感器异常,读数${mqttTemperature}°C超出控制范围${this.temperatureRange.min}°C-${this.temperatureRange.max}°C`,
|
// content: `温度传感器异常,读数${mqttTemperature}°C超出控制范围${this.temperatureRange.min}°C-${this.temperatureRange.max}°C`,
|
||||||
content: `温度超出控制范围`,
|
content: `温度超出控制范围`,
|
||||||
@ -660,21 +684,21 @@ export default {
|
|||||||
// 记录报警到控制台并调用创建告警接口
|
// 记录报警到控制台并调用创建告警接口
|
||||||
async logAlert(alert) {
|
async logAlert(alert) {
|
||||||
console.log('🚨 报警触发:', JSON.stringify(alert, null, 2))
|
console.log('🚨 报警触发:', JSON.stringify(alert, null, 2))
|
||||||
this.alertHistory.push(alert)
|
// this.alertHistory.push(alert)
|
||||||
|
|
||||||
// 调用创建告警接口
|
// // 调用创建告警接口
|
||||||
try {
|
// try {
|
||||||
console.log('📤 正在创建告警记录...')
|
// console.log('📤 正在创建告警记录...')
|
||||||
const response = await alertApi.create(alert)
|
// const response = await alertApi.create(alert)
|
||||||
console.log('✅ 告警记录创建成功:', response)
|
// console.log('✅ 告警记录创建成功:', response)
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error('❌ 告警记录创建失败:', error)
|
// console.error('❌ 告警记录创建失败:', error)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 限制报警历史记录数量,避免内存溢出
|
// // 限制报警历史记录数量,避免内存溢出
|
||||||
if (this.alertHistory.length > 100) {
|
// if (this.alertHistory.length > 100) {
|
||||||
this.alertHistory = this.alertHistory.slice(-50)
|
// this.alertHistory = this.alertHistory.slice(-50)
|
||||||
}
|
// }
|
||||||
},
|
},
|
||||||
|
|
||||||
// 降低目标温度
|
// 降低目标温度
|
||||||
@ -875,7 +899,7 @@ export default {
|
|||||||
this.humidityValidationClass = ''
|
this.humidityValidationClass = ''
|
||||||
},
|
},
|
||||||
|
|
||||||
// 显示湿度变化提示
|
// 空调湿度更新
|
||||||
showHumidityChangeToast() {
|
showHumidityChangeToast() {
|
||||||
// 发送空调参数到MQTT
|
// 发送空调参数到MQTT
|
||||||
this.sendAirConditionerParams()
|
this.sendAirConditionerParams()
|
||||||
@ -889,110 +913,156 @@ export default {
|
|||||||
this.checkAlerts(mockMqttData)
|
this.checkAlerts(mockMqttData)
|
||||||
},
|
},
|
||||||
|
|
||||||
// 显示温度变化提示
|
// 空调温度更新
|
||||||
showTemperatureChangeToast() {
|
showTemperatureChangeToast() {
|
||||||
// uni.showToast({
|
// 发送空调参数到MQTT
|
||||||
// title: `目标温度: ${this.targetTemperature}°C`,
|
this.sendAirConditionerParams()
|
||||||
// icon: 'success',
|
|
||||||
// duration: 1500
|
|
||||||
// })
|
|
||||||
|
|
||||||
// 发送空调参数到MQTT
|
// 温度变化后检查报警(使用当前页面数据模拟MQTT数据)
|
||||||
this.sendAirConditionerParams()
|
const mockMqttData = {
|
||||||
|
deviceType: 'WSD',
|
||||||
|
temperature: this.temperature,
|
||||||
|
humidity: this.humidity
|
||||||
|
}
|
||||||
|
this.checkAlerts(mockMqttData)
|
||||||
|
},
|
||||||
|
|
||||||
// 温度变化后检查报警(使用当前页面数据模拟MQTT数据)
|
// 发送空调参数
|
||||||
const mockMqttData = {
|
async sendAirConditionerParams() {
|
||||||
deviceType: 'WSD',
|
try {
|
||||||
temperature: this.temperature,
|
// 根据MQTT文档,空调温度使用BSQWD,但控制指令可能使用不同的TagName
|
||||||
humidity: this.humidity
|
// 发送温度参数 - 使用BSQWD作为TagName(与接收数据保持一致)
|
||||||
|
const temperatureData = {
|
||||||
|
"TagValue": this.targetTemperature,
|
||||||
|
"TagName": "JS_COD", // 使用与接收数据一致的TagName
|
||||||
|
"method": "setValue"
|
||||||
}
|
}
|
||||||
this.checkAlerts(mockMqttData)
|
|
||||||
},
|
|
||||||
|
|
||||||
// 发送空调参数
|
// 发送湿度参数 - 根据文档WSD设备湿度使用SD
|
||||||
async sendAirConditionerParams() {
|
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 {
|
try {
|
||||||
// 根据MQTT文档,空调温度使用BSQWD,但控制指令可能使用不同的TagName
|
await this.submitTemperatureData()
|
||||||
// 发送温度参数 - 使用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)
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '参数设置失败',
|
title: '参数设置成功',
|
||||||
icon: 'error',
|
icon: 'success',
|
||||||
duration: 2000
|
duration: 1500
|
||||||
|
})
|
||||||
|
} catch (apiError) {
|
||||||
|
// MQTT发送成功,但接口保存失败
|
||||||
|
console.warn('⚠️ MQTT发送成功,但接口保存失败:', apiError)
|
||||||
|
uni.showToast({
|
||||||
|
title: 'MQTT已发送,接口保存失败',
|
||||||
|
icon: 'none',
|
||||||
|
duration: 2500
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
// 提交温湿度数据
|
} else {
|
||||||
async submitTemperatureData() {
|
console.error('❌ 空调参数MQTT发送失败')
|
||||||
try {
|
uni.showToast({
|
||||||
const temperatureHumidityData = {
|
title: 'MQTT发送失败',
|
||||||
temperature: this.targetTemperature,
|
icon: 'error',
|
||||||
humidity: this.targetHumidity,
|
duration: 2000
|
||||||
deviceId: "TH_SENSOR_001",
|
})
|
||||||
timestamp: new Date().toISOString(),
|
}
|
||||||
source: "manual_setting" // 标识为手动设置
|
} catch (error) {
|
||||||
}
|
console.error('❌ 发送空调参数异常:', error)
|
||||||
console.log('📤 提交温湿度数据到接口:', temperatureHumidityData)
|
uni.showToast({
|
||||||
const response = await thDataApi.submit(temperatureHumidityData)
|
title: '参数设置失败',
|
||||||
console.log('✅ 温湿度数据接口提交成功:', response)
|
icon: 'error',
|
||||||
} catch (error) {
|
duration: 2000
|
||||||
console.error('❌ 温湿度数据接口提交失败:', error)
|
})
|
||||||
// 接口失败不影响MQTT发送,只记录日志
|
}
|
||||||
throw error // 重新抛出错误,让调用方知道接口失败了
|
},
|
||||||
}
|
|
||||||
},
|
// 空调开机控制
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// 手动重连MQTT
|
||||||
manualReconnect() {
|
manualReconnect() {
|
||||||
@ -1449,6 +1519,58 @@ export default {
|
|||||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
|
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 {
|
.ac-temp-display {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
|||||||
@ -25,31 +25,6 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 视频信息区域 -->
|
|
||||||
<view v-if="ezstate" class="video-info">
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">📡 设备状态</text>
|
|
||||||
<text class="info-value online">在线</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">🎥 分辨率</text>
|
|
||||||
<text class="info-value">高清</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">🔊 音频</text>
|
|
||||||
<text class="info-value">开启</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 视频控制按钮 -->
|
|
||||||
<view v-if="ezstate" class="control-section">
|
|
||||||
<button @click="handleInitPlayer" class="control-btn init-btn">
|
|
||||||
🔄 初始化
|
|
||||||
</button>
|
|
||||||
<button @click="handleTogglePlay" class="control-btn pause-btn">
|
|
||||||
{{ isPlaying ? '⏸ 暂停播放' : '▶ 开始播放' }}
|
|
||||||
</button>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- API测试按钮 -->
|
<!-- API测试按钮 -->
|
||||||
<view class="test-section" v-if="!ezstate">
|
<view class="test-section" v-if="!ezstate">
|
||||||
@ -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) {
|
handlePlayStateChange(isPlaying) {
|
||||||
|
|||||||
@ -9,7 +9,14 @@ class MqttDataManager {
|
|||||||
temperature: null,
|
temperature: null,
|
||||||
humidity: null,
|
humidity: null,
|
||||||
pm25: null,
|
pm25: null,
|
||||||
timestamp: null
|
timestamp: null,
|
||||||
|
// 扩展数据结构支持更多设备类型
|
||||||
|
deviceType: null,
|
||||||
|
rawData: null,
|
||||||
|
// 空调设备数据
|
||||||
|
acTemperature: null,
|
||||||
|
// 其他设备数据可在此扩展
|
||||||
|
otherDeviceData: {}
|
||||||
}
|
}
|
||||||
this.init()
|
this.init()
|
||||||
}
|
}
|
||||||
@ -95,41 +102,65 @@ class MqttDataManager {
|
|||||||
// console.log('设备数据:', deviceDataContent)
|
// console.log('设备数据:', deviceDataContent)
|
||||||
// console.log('时间戳:', timestamp)
|
// console.log('时间戳:', timestamp)
|
||||||
|
|
||||||
// 根据设备类型处理数据
|
// 处理所有设备类型的数据
|
||||||
if (deviceType === 'WSD') {
|
console.log(`✅ 处理设备类型: ${deviceType}`)
|
||||||
// console.log('✅ 处理WSD设备数据 - 更新环境参数')
|
this.processAllDeviceData(deviceType, deviceDataContent, timestamp)
|
||||||
this.processWSDData(deviceDataContent, timestamp)
|
|
||||||
} else {
|
|
||||||
// console.log(`⚠️ 设备类型 ${deviceType} 暂不处理,仅打印到控制台`)
|
|
||||||
// console.log('设备详情:', {
|
|
||||||
// deviceType,
|
|
||||||
// data: deviceDataContent,
|
|
||||||
// timestamp: new Date(timestamp * 1000).toLocaleString('zh-CN')
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 处理设备数据失败:', error)
|
console.error('❌ 处理设备数据失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理WSD设备数据
|
// 处理所有设备类型的数据
|
||||||
processWSDData(data, timestamp) {
|
processAllDeviceData(deviceType, data, timestamp) {
|
||||||
try {
|
try {
|
||||||
// 解析WSD数据 - 根据您提供的数据结构,WD是温度,SD是湿度
|
console.log(`🌡️ ${deviceType}设备数据解析:`)
|
||||||
const temperature = data.WD && parseFloat(data.WD);
|
console.log('设备数据:', data)
|
||||||
const humidity = data.SD && parseFloat(data.SD);
|
|
||||||
|
|
||||||
// console.log('🌡️ WSD数据解析:')
|
// 构建基础解析数据
|
||||||
// console.log('温度(WD):', temperature)
|
|
||||||
// console.log('湿度(SD):', humidity)
|
|
||||||
|
|
||||||
// 构建解析后的数据
|
|
||||||
const parsedData = {
|
const parsedData = {
|
||||||
deviceType: 'WSD',
|
deviceType,
|
||||||
timestamp,
|
timestamp,
|
||||||
time: new Date(timestamp * 1000).toLocaleString('zh-CN'),
|
time: new Date(timestamp * 1000).toLocaleString('zh-CN'),
|
||||||
temperature,
|
rawData: data // 保存原始数据供页面特殊处理
|
||||||
humidity
|
}
|
||||||
|
|
||||||
|
// 根据设备类型解析特定数据
|
||||||
|
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)
|
this.notifyListeners('dataUpdate', parsedData)
|
||||||
|
|
||||||
// console.log('✅ WSD数据处理完成:', parsedData)
|
console.log(`✅ ${deviceType}数据处理完成:`, parsedData)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 处理WSD数据失败:', error)
|
console.error(`❌ 处理${deviceType}数据失败:`, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,16 +229,18 @@ class MqttDataManager {
|
|||||||
|
|
||||||
// 更新最新数据
|
// 更新最新数据
|
||||||
updateLastData(parsedData) {
|
updateLastData(parsedData) {
|
||||||
if (parsedData.temperature !== undefined) {
|
// 直接合并数据,只更新有值的字段
|
||||||
this.lastData.temperature = parsedData.temperature
|
Object.keys(parsedData).forEach(key => {
|
||||||
}
|
if (parsedData[key] !== undefined) {
|
||||||
if (parsedData.humidity !== undefined) {
|
if (key === 'otherDeviceData' && this.lastData.otherDeviceData) {
|
||||||
this.lastData.humidity = parsedData.humidity
|
// 对于 otherDeviceData,进行深度合并
|
||||||
}
|
this.lastData.otherDeviceData = { ...this.lastData.otherDeviceData, ...parsedData.otherDeviceData }
|
||||||
if (parsedData.pm25 !== undefined) {
|
} else {
|
||||||
this.lastData.pm25 = parsedData.pm25
|
// 其他字段直接赋值
|
||||||
}
|
this.lastData[key] = parsedData[key]
|
||||||
this.lastData.timestamp = parsedData.timestamp
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加数据监听器
|
// 添加数据监听器
|
||||||
|
|||||||
572
萤石云APP对接完整指南.md
572
萤石云APP对接完整指南.md
@ -271,591 +271,21 @@ src/
|
|||||||
|
|
||||||
### 1. 播放器组件 (`EzvizVideoPlayerSimple.vue`)
|
### 1. 播放器组件 (`EzvizVideoPlayerSimple.vue`)
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<view class="simple-video-player">
|
|
||||||
<!-- APP平台使用web-view -->
|
|
||||||
<web-view
|
|
||||||
v-if="webviewUrl"
|
|
||||||
:src="webviewUrl"
|
|
||||||
class="video-webview"
|
|
||||||
></web-view>
|
|
||||||
|
|
||||||
<!-- 控制按钮 -->
|
|
||||||
<view class="control-buttons" v-if="!loading && !error">
|
|
||||||
<button class="control-btn play-btn" @click="togglePlay">
|
|
||||||
{{ isPlaying ? '⏸ 暂停' : '▶ 播放' }}
|
|
||||||
</button>
|
|
||||||
<button class="control-btn refresh-btn" @click="refresh">
|
|
||||||
🔄 刷新
|
|
||||||
</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'EzvizVideoPlayerSimple',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
webviewUrl: '',
|
|
||||||
isPlaying: true,
|
|
||||||
config: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
initEzuikit(config) {
|
|
||||||
console.log('[播放器] 初始化')
|
|
||||||
|
|
||||||
if (!config || !config.accessToken || !config.play_url) {
|
|
||||||
console.error('配置参数不完整')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.config = config
|
|
||||||
|
|
||||||
// 构建 URL,通过 URL 参数传递配置
|
|
||||||
const token = encodeURIComponent(config.accessToken)
|
|
||||||
const url = encodeURIComponent(config.play_url)
|
|
||||||
this.webviewUrl = `/static/html/ezviz-iframe.html?accessToken=${token}&playUrl=${url}`
|
|
||||||
|
|
||||||
console.log('[播放器] URL已设置')
|
|
||||||
},
|
|
||||||
|
|
||||||
togglePlay() {
|
|
||||||
if (this.isPlaying) {
|
|
||||||
// 暂停:清空 URL
|
|
||||||
this.webviewUrl = ''
|
|
||||||
this.isPlaying = false
|
|
||||||
} else {
|
|
||||||
// 播放:重新加载
|
|
||||||
if (this.config) {
|
|
||||||
const token = encodeURIComponent(this.config.accessToken)
|
|
||||||
const url = encodeURIComponent(this.config.play_url)
|
|
||||||
this.webviewUrl = `/static/html/ezviz-iframe.html?accessToken=${token}&playUrl=${url}`
|
|
||||||
this.isPlaying = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
refresh() {
|
|
||||||
console.log('[播放器] 刷新')
|
|
||||||
this.webviewUrl = ''
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.config) {
|
|
||||||
const token = encodeURIComponent(this.config.accessToken)
|
|
||||||
const url = encodeURIComponent(this.config.play_url)
|
|
||||||
this.webviewUrl = `/static/html/ezviz-iframe.html?accessToken=${token}&playUrl=${url}`
|
|
||||||
}
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. iframe HTML (`ezviz-iframe.html`)
|
### 2. iframe HTML (`ezviz-iframe.html`)
|
||||||
|
|
||||||
```html
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
||||||
<title>萤石云播放器</title>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html, body {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #1a1a1a;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
#player-iframe {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border: none;
|
|
||||||
display: block;
|
|
||||||
object-fit: contain; /* 保持视频比例,不变形 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
color: white;
|
|
||||||
font-size: 14px;
|
|
||||||
text-align: center;
|
|
||||||
background: rgba(0, 0, 0, 0.7);
|
|
||||||
padding: 15px 30px;
|
|
||||||
border-radius: 8px;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading::after {
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
margin: 10px auto 0;
|
|
||||||
border: 3px solid rgba(255, 255, 255, 0.3);
|
|
||||||
border-top-color: white;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="loading" id="loading">正在加载播放器...</div>
|
|
||||||
<iframe id="player-iframe" allow="autoplay; fullscreen"></iframe>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function log(message) {
|
|
||||||
console.log('[iframe播放器] ' + message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从URL参数获取配置
|
|
||||||
function getConfig() {
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
return {
|
|
||||||
accessToken: params.get('accessToken'),
|
|
||||||
playUrl: params.get('playUrl')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化播放器
|
|
||||||
function init() {
|
|
||||||
log('初始化开始');
|
|
||||||
|
|
||||||
const config = getConfig();
|
|
||||||
|
|
||||||
if (!config.accessToken || !config.playUrl) {
|
|
||||||
log('配置参数不完整');
|
|
||||||
document.getElementById('loading').textContent = '配置参数错误';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log('AccessToken: ' + config.accessToken.substring(0, 20) + '...');
|
|
||||||
log('PlayUrl: ' + config.playUrl);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 使用萤石云官方iframe播放器(内存占用小)
|
|
||||||
const iframeUrl = 'https://open.ys7.com/ezopen/h5/iframe?' +
|
|
||||||
'url=' + encodeURIComponent(config.playUrl) +
|
|
||||||
'&accessToken=' + encodeURIComponent(config.accessToken) +
|
|
||||||
'&width=100%' +
|
|
||||||
'&height=100%' +
|
|
||||||
'&autoplay=1' +
|
|
||||||
'&audio=1' +
|
|
||||||
'&controls=1';
|
|
||||||
|
|
||||||
log('iframe URL: ' + iframeUrl);
|
|
||||||
|
|
||||||
const iframe = document.getElementById('player-iframe');
|
|
||||||
iframe.src = iframeUrl;
|
|
||||||
|
|
||||||
// 隐藏loading
|
|
||||||
setTimeout(() => {
|
|
||||||
document.getElementById('loading').style.display = 'none';
|
|
||||||
log('播放器加载完成');
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
log('初始化失败: ' + error.message);
|
|
||||||
document.getElementById('loading').textContent = '初始化失败';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面加载完成后初始化
|
|
||||||
window.onload = function() {
|
|
||||||
log('页面加载完成');
|
|
||||||
init();
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. 监控页面 (`pages/visual/index.vue`)
|
### 3. 监控页面 (`pages/visual/index.vue`)
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<view class="visual-monitoring-page">
|
|
||||||
<!-- 固定头部 -->
|
|
||||||
<view class="fixed-header">
|
|
||||||
<text class="header-title">移动式检修车间</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
|
||||||
<view class="tabbar-content">
|
|
||||||
<!-- 视频播放区域 - 保持16:9比例 -->
|
|
||||||
<view v-if="ezstate" class="video-wrapper">
|
|
||||||
<view class="video-content">
|
|
||||||
<EzvizVideoPlayer ref="playerVideoRef"></EzvizVideoPlayer>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 暂无数据 -->
|
|
||||||
<view v-else class="no-data-container">
|
|
||||||
<view class="no-data-icon">📹</view>
|
|
||||||
<text class="no-data-text">暂无监控数据</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 视频信息 -->
|
|
||||||
<view v-if="ezstate" class="video-info">
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">📡 设备状态</text>
|
|
||||||
<text class="info-value online">在线</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">🎥 分辨率</text>
|
|
||||||
<text class="info-value">高清</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">🔊 音频</text>
|
|
||||||
<text class="info-value">开启</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import EzvizVideoPlayer from '@/components/EzvizVideoPlayerSimple.vue'
|
|
||||||
import tokenManager from '@/utils/ezvizTokenManager.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
EzvizVideoPlayer
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
ezstate: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLoad() {
|
|
||||||
console.log('监控页面加载')
|
|
||||||
this.getVideoData()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async getVideoData() {
|
|
||||||
try {
|
|
||||||
// 获取 AccessToken
|
|
||||||
let accessToken
|
|
||||||
try {
|
|
||||||
accessToken = await tokenManager.getValidAccessToken()
|
|
||||||
console.log('✅ AccessToken获取成功')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ AccessToken获取失败,使用备用token')
|
|
||||||
accessToken = "your-backup-access-token"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 配置参数
|
|
||||||
const ezuikitInfo = {
|
|
||||||
accessToken: accessToken,
|
|
||||||
play_url: "ezopen://open.ys7.com/K74237657/1.hd.live"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 先启用视频状态,让组件渲染
|
|
||||||
this.ezstate = true
|
|
||||||
|
|
||||||
// 等待组件渲染完成后初始化播放器
|
|
||||||
await this.$nextTick()
|
|
||||||
|
|
||||||
// 确保ref存在后再调用
|
|
||||||
if (this.$refs.playerVideoRef) {
|
|
||||||
this.$refs.playerVideoRef.initEzuikit(ezuikitInfo)
|
|
||||||
} else {
|
|
||||||
console.error('❌ 播放器组件未找到')
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('初始化视频失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.visual-monitoring-page {
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
background: #f5f6fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fixed-header {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 100rpx;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 1000;
|
|
||||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabbar-content {
|
|
||||||
width: 100%;
|
|
||||||
height: calc(100vh - 100rpx - 100rpx);
|
|
||||||
margin-top: 100rpx;
|
|
||||||
padding: 30rpx;
|
|
||||||
overflow-y: auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 30rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 视频内容区域 - 保持16:9宽高比,不变形 */
|
|
||||||
.video-content {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
position: relative;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.2);
|
|
||||||
background: #000;
|
|
||||||
|
|
||||||
/* 使用padding-top技巧保持16:9宽高比 */
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
padding-top: 56.25%; /* 16:9 = 9/16 = 0.5625 = 56.25% */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 播放器绝对定位填充容器 */
|
|
||||||
:deep(.simple-video-player) {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-data-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 400rpx;
|
|
||||||
background: linear-gradient(135deg, #f5f7fa 0%, #e3e7f0 100%);
|
|
||||||
border-radius: 16rpx;
|
|
||||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-data-icon {
|
|
||||||
font-size: 120rpx;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-data-text {
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-info {
|
|
||||||
display: flex;
|
|
||||||
gap: 20rpx;
|
|
||||||
padding: 20rpx 30rpx;
|
|
||||||
background: white;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8rpx;
|
|
||||||
padding: 15rpx 10rpx;
|
|
||||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
|
||||||
border-radius: 10rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-label {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #666;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value {
|
|
||||||
font-size: 26rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
|
|
||||||
&.online {
|
|
||||||
color: #4caf50;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. AccessToken 管理 (`utils/ezvizTokenManager.js`)
|
### 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(横屏配置)
|
### 1. pages.json(横屏配置)
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"pages": [
|
|
||||||
{
|
|
||||||
"path": "pages/visual/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "移动式检修车间",
|
|
||||||
"navigationStyle": "custom",
|
|
||||||
"pageOrientation": "landscape" // ← 横屏展示
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. manifest.json(内存配置)
|
### 2. manifest.json(内存配置)
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"app-plus": {
|
|
||||||
"compatible": {
|
|
||||||
"largeHeap": true // ← 启用大内存堆(512MB)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. 萤石云参数说明
|
### 3. 萤石云参数说明
|
||||||
|
|
||||||
#### AccessToken 获取
|
#### 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 播放地址格式
|
||||||
```
|
```
|
||||||
ezopen://open.ys7.com/{设备序列号}/{通道号}.{清晰度}.live
|
ezopen://open.ys7.com/{设备序列号}/{通道号}.{清晰度}.live
|
||||||
@ -1223,3 +653,5 @@ onHide() {
|
|||||||
|
|
||||||
🎉 **恭喜!萤石云APP对接完成!** 🎉
|
🎉 **恭喜!萤石云APP对接完成!** 🎉
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user