萤石云对接、温湿度卡片合并、接口更新
This commit is contained in:
119
src/App.vue
119
src/App.vue
@ -3,7 +3,6 @@ import mqttDataManager from '@/utils/mqttDataManager.js'
|
||||
|
||||
export default {
|
||||
onLaunch: function () {
|
||||
console.log('App Launch')
|
||||
// 应用启动时的初始化逻辑
|
||||
this.initApp()
|
||||
},
|
||||
@ -26,17 +25,17 @@ export default {
|
||||
}
|
||||
|
||||
// 显示平台信息
|
||||
console.log('📱 当前平台:',
|
||||
// #ifdef H5
|
||||
'H5'
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
'APP-PLUS'
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
'MP-WEIXIN'
|
||||
// #endif
|
||||
)
|
||||
let platform = 'Unknown'
|
||||
// #ifdef H5
|
||||
platform = 'H5'
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
platform = 'APP-PLUS'
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
platform = 'MP-WEIXIN'
|
||||
// #endif
|
||||
console.log('📱 当前平台:', platform)
|
||||
|
||||
// MQTT连接已在mqttDataManager中自动初始化
|
||||
console.log('✅ 应用初始化完成')
|
||||
@ -55,7 +54,7 @@ page {
|
||||
sans-serif;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #f5f5f5;
|
||||
background-color: #f5f6fa;
|
||||
}
|
||||
|
||||
/* 确保根元素和页面容器都是100%高度 */
|
||||
@ -74,15 +73,16 @@ page {
|
||||
|
||||
/* 固定头部样式 */
|
||||
.fixed-header {
|
||||
// position: fixed;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// right: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 40px;
|
||||
z-index: 1000;
|
||||
background-color: #3f51b5;
|
||||
padding: 20rpx 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
|
||||
background-color: #ffffff;
|
||||
padding: 15rpx 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
|
||||
border-bottom: 2rpx solid #e1e5e9;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
@ -90,9 +90,9 @@ page {
|
||||
}
|
||||
|
||||
.header-title {
|
||||
color: white;
|
||||
color: #2c3e50;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -102,7 +102,7 @@ page {
|
||||
padding-top: 100rpx; /* 为固定头部留出空间 */
|
||||
padding-bottom: 200rpx; /* 为tabbar留出空间 */
|
||||
overflow-y: auto;
|
||||
background-color: #f5f5f5;
|
||||
background-color: #f5f6fa;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@ -110,13 +110,14 @@ page {
|
||||
/* tabbar页面内容区域 */
|
||||
.tabbar-content {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
// padding: 0 20rpx; /* 增加底部padding为tabbar留出空间 */
|
||||
margin-top: 100rpx; /* 为固定头部留出空间,增加距离 */
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// padding-bottom: 60px
|
||||
// min-height: calc(100vh - 200rpx); /* 调整最小高度计算 */
|
||||
// #ifdef H5
|
||||
margin-bottom: 50px;
|
||||
// margin-bottom: 50px;
|
||||
// #endif
|
||||
}
|
||||
|
||||
@ -148,29 +149,69 @@ button::after {
|
||||
|
||||
/* 卡片样式 */
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 12rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05);
|
||||
background: #ffffff;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
|
||||
border: 1rpx solid #e1e5e9;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn-primary {
|
||||
background-color: #3f51b5;
|
||||
background-color: #2980b9;
|
||||
color: white;
|
||||
padding: 20rpx 40rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
border-radius: 6rpx;
|
||||
font-size: 26rpx;
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-primary:active {
|
||||
background-color: #21618c;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #666;
|
||||
background-color: #7f8c8d;
|
||||
color: white;
|
||||
padding: 20rpx 40rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
border-radius: 6rpx;
|
||||
font-size: 26rpx;
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-secondary:active {
|
||||
background-color: #6c7b7d;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media screen and (max-width: 750rpx) {
|
||||
.fixed-header {
|
||||
padding: 16rpx 20rpx;
|
||||
}
|
||||
|
||||
.tabbar-content {
|
||||
padding: 16rpx 16rpx 100rpx; /* 调整小屏幕下的内边距 */
|
||||
margin-top: 90rpx; /* 调整小屏幕下的顶部间距 */
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600rpx) {
|
||||
.fixed-header {
|
||||
padding: 12rpx 16rpx;
|
||||
}
|
||||
|
||||
.tabbar-content {
|
||||
padding: 16rpx 16rpx 90rpx; /* 更小屏幕下的内边距 */
|
||||
margin-top: 80rpx; /* 更小屏幕下的顶部间距 */
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
351
src/components/EzvizVideoPlayerSimple.vue
Normal file
351
src/components/EzvizVideoPlayerSimple.vue
Normal file
@ -0,0 +1,351 @@
|
||||
<template>
|
||||
<view class="simple-video-player">
|
||||
<view class="debug-info" v-if="showDebug">
|
||||
<text>平台: {{ platform }}</text>
|
||||
<text>状态: {{ status }}</text>
|
||||
<text>播放状态: {{ isPlaying ? '播放中' : '已暂停' }}</text>
|
||||
</view>
|
||||
|
||||
<!-- APP平台使用web-view -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<web-view
|
||||
v-if="webviewUrl"
|
||||
ref="videoWebview"
|
||||
:src="webviewUrl"
|
||||
class="video-webview"
|
||||
@message="handleMessage"
|
||||
></web-view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- H5平台提示 -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="h5-tip">H5平台暂不支持</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 控制按钮 -->
|
||||
<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 v-if="loading" class="loading">
|
||||
<text>{{ loadingText }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="error" class="error">
|
||||
<text>{{ errorText }}</text>
|
||||
<button @click="retry">重试</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'EzvizVideoPlayerSimple',
|
||||
props: {
|
||||
showDebug: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
platform: '',
|
||||
status: '未初始化',
|
||||
webviewUrl: '',
|
||||
loading: false,
|
||||
loadingText: '',
|
||||
error: false,
|
||||
errorText: '',
|
||||
config: null,
|
||||
isPlaying: true // 默认自动播放
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.detectPlatform()
|
||||
},
|
||||
methods: {
|
||||
detectPlatform() {
|
||||
// #ifdef H5
|
||||
this.platform = 'H5'
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
this.platform = 'APP'
|
||||
// #endif
|
||||
|
||||
console.log('[简单播放器] 平台:', this.platform)
|
||||
},
|
||||
|
||||
initEzuikit(config) {
|
||||
console.log('[简单播放器] 初始化:', config)
|
||||
|
||||
if (!config || !config.accessToken || !config.play_url) {
|
||||
this.error = true
|
||||
this.errorText = '配置参数不完整'
|
||||
return
|
||||
}
|
||||
|
||||
this.config = config
|
||||
this.loading = true
|
||||
this.loadingText = '正在加载播放器...'
|
||||
this.status = '加载中'
|
||||
|
||||
try {
|
||||
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('[简单播放器] 使用iframe版本,URL已设置')
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.loading) {
|
||||
this.loading = false
|
||||
this.status = '播放器已加载'
|
||||
}
|
||||
}, 3000)
|
||||
|
||||
} catch (err) {
|
||||
console.error('[简单播放器] 错误:', err)
|
||||
this.error = true
|
||||
this.errorText = err.message
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
handleMessage(event) {
|
||||
console.log('[简单播放器] 收到消息:', event)
|
||||
|
||||
try {
|
||||
const data = event.detail.data
|
||||
const msg = Array.isArray(data) ? data[0] : data
|
||||
|
||||
if (msg && msg.type === 'success') {
|
||||
this.loading = false
|
||||
this.status = '播放成功'
|
||||
} else if (msg && msg.type === 'error') {
|
||||
this.loading = false
|
||||
this.error = true
|
||||
this.errorText = msg.message || '播放失败'
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[简单播放器] 消息处理错误:', err)
|
||||
}
|
||||
},
|
||||
|
||||
retry() {
|
||||
this.error = false
|
||||
this.errorText = ''
|
||||
if (this.config) {
|
||||
this.initEzuikit(this.config)
|
||||
}
|
||||
},
|
||||
|
||||
// 切换播放/暂停
|
||||
togglePlay() {
|
||||
console.log('[简单播放器] 切换播放状态:', this.isPlaying ? '暂停' : '播放')
|
||||
|
||||
// 通过重新加载URL来实现播放/暂停
|
||||
// 因为iframe播放器不支持直接控制,所以采用重新加载的方式
|
||||
if (this.isPlaying) {
|
||||
// 暂停:清空URL
|
||||
this.webviewUrl = ''
|
||||
this.isPlaying = false
|
||||
this.status = '已暂停'
|
||||
|
||||
// 触发状态变化事件
|
||||
this.$emit('playStateChange', false)
|
||||
} else {
|
||||
// 播放:重新设置URL
|
||||
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
|
||||
this.status = '播放中'
|
||||
|
||||
// 触发状态变化事件
|
||||
this.$emit('playStateChange', true)
|
||||
}
|
||||
}
|
||||
|
||||
// 返回新的播放状态
|
||||
return this.isPlaying
|
||||
},
|
||||
|
||||
// 获取当前播放状态
|
||||
getPlayState() {
|
||||
return this.isPlaying
|
||||
},
|
||||
|
||||
// 刷新播放器
|
||||
refresh() {
|
||||
console.log('[简单播放器] 刷新播放器')
|
||||
|
||||
if (this.config) {
|
||||
uni.showToast({
|
||||
title: '正在刷新...',
|
||||
icon: 'loading',
|
||||
duration: 1000
|
||||
})
|
||||
|
||||
// 先清空再重新加载
|
||||
this.webviewUrl = ''
|
||||
|
||||
setTimeout(() => {
|
||||
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
|
||||
this.status = '播放中'
|
||||
|
||||
uni.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.simple-video-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.debug-info {
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
left: 10rpx;
|
||||
background: rgba(0,0,0,0.7);
|
||||
color: white;
|
||||
padding: 10rpx;
|
||||
font-size: 24rpx;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.debug-info text {
|
||||
display: block;
|
||||
margin: 5rpx 0;
|
||||
}
|
||||
|
||||
.video-webview {
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
.h5-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: white;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0,0,0,0.8);
|
||||
color: white;
|
||||
font-size: 28rpx;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.error {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255,0,0,0.8);
|
||||
color: white;
|
||||
font-size: 28rpx;
|
||||
z-index: 50;
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.error text {
|
||||
margin-bottom: 30rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error button {
|
||||
background: white;
|
||||
color: #333;
|
||||
padding: 20rpx 40rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
/* 控制按钮 */
|
||||
.control-buttons {
|
||||
position: absolute;
|
||||
bottom: 30rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
color: white;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.6);
|
||||
padding: 16rpx 32rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
min-width: 140rpx;
|
||||
backdrop-filter: blur(10rpx);
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.control-btn:active {
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
.play-btn {
|
||||
background: rgba(46, 125, 50, 0.85);
|
||||
border-color: rgba(76, 175, 80, 0.9);
|
||||
}
|
||||
|
||||
.play-btn:active {
|
||||
background: rgba(46, 125, 50, 1);
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
background: rgba(25, 118, 210, 0.85);
|
||||
border-color: rgba(33, 150, 243, 0.9);
|
||||
}
|
||||
|
||||
.refresh-btn:active {
|
||||
background: rgba(25, 118, 210, 1);
|
||||
}
|
||||
</style>
|
||||
|
||||
56
src/main.js
56
src/main.js
@ -1,28 +1,28 @@
|
||||
import {
|
||||
createSSRApp
|
||||
} from "vue";
|
||||
import App from "./App.vue";
|
||||
import httpService from "./utils/http.js";
|
||||
import api from "./utils/api.js";
|
||||
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App);
|
||||
|
||||
// 注册全局HTTP服务
|
||||
app.config.globalProperties.$http = httpService;
|
||||
app.config.globalProperties.$api = api;
|
||||
|
||||
return {
|
||||
app,
|
||||
};
|
||||
}
|
||||
|
||||
// #ifndef MP
|
||||
// 处理 wx.connectSocket promisify 兼容问题,强制返回 SocketTask
|
||||
uni.connectSocket = (function(connectSocket) {
|
||||
return function(options) {
|
||||
options.success = options.success || function() {}
|
||||
return connectSocket.call(this, options)
|
||||
}
|
||||
})(uni.connectSocket)
|
||||
// #endif
|
||||
import {
|
||||
createSSRApp
|
||||
} from "vue";
|
||||
import App from "./App.vue";
|
||||
import httpService from "./utils/http.js";
|
||||
import api from "./utils/api.js";
|
||||
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App);
|
||||
|
||||
// 注册全局HTTP服务
|
||||
app.config.globalProperties.$http = httpService;
|
||||
app.config.globalProperties.$api = api;
|
||||
|
||||
return {
|
||||
app,
|
||||
};
|
||||
}
|
||||
|
||||
// #ifndef MP
|
||||
// 处理 wx.connectSocket promisify 兼容问题,强制返回 SocketTask
|
||||
uni.connectSocket = (function(connectSocket) {
|
||||
return function(options) {
|
||||
options.success = options.success || function() {}
|
||||
return connectSocket.call(this, options)
|
||||
}
|
||||
})(uni.connectSocket)
|
||||
// #endif
|
||||
|
||||
@ -11,6 +11,9 @@
|
||||
"nvueStyleCompiler" : "uni-app",
|
||||
"compilerVersion" : 2,
|
||||
"orientation" : "portrait",
|
||||
"compatible" : {
|
||||
"largeHeap" : true
|
||||
},
|
||||
"icons" : {
|
||||
"app" : {
|
||||
"hdpi" : "static/app-icon.png",
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
"path": "pages/visual/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "移动式检修车间",
|
||||
"navigationStyle": "custom"
|
||||
"pageOrientation": "landscape"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -45,23 +45,23 @@
|
||||
"path": "pages/system/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "移动式检修车间",
|
||||
"navigationStyle": "custom",
|
||||
"orientation": "landscape"
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "移动式检修车间系统",
|
||||
"navigationBarBackgroundColor": "#3f51b5",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"backgroundColor": "#f5f6fa"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#666666",
|
||||
"selectedColor": "#3f51b5",
|
||||
"color": "#7f8c8d",
|
||||
"selectedColor": "#2980b9",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderStyle": "black",
|
||||
"fontSize": "14px",
|
||||
"borderStyle": "white",
|
||||
"fontSize": "13px",
|
||||
"height": "65px",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/environment/index",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -7,17 +7,22 @@
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view class="tabbar-content">
|
||||
<!-- 日期选择器 -->
|
||||
<!-- 日期导航器 -->
|
||||
<view class="date-selector">
|
||||
<picker mode="date" :value="selectedDate" @change="onDateChange">
|
||||
<view class="date-picker">
|
||||
<view class="date-navigation">
|
||||
<button class="nav-button prev-button" @click="goToPreviousDay">
|
||||
<!-- <text class="nav-icon">‹</text> -->
|
||||
<text class="nav-text">上一天</text>
|
||||
</button>
|
||||
<view class="current-date">
|
||||
<text class="date-text">{{ selectedDate }}</text>
|
||||
<text class="picker-arrow">▼</text>
|
||||
<!-- <text class="date-weekday">{{ getWeekday(selectedDate) }}</text> -->
|
||||
</view>
|
||||
</picker>
|
||||
<!-- <view class="data-status" :class="dataStatus.dataSource">
|
||||
<text class="status-text">{{ dataStatus.dataSource === 'api' ? '实时数据' : '示例数据' }}</text>
|
||||
</view> -->
|
||||
<button class="nav-button next-button" @click="goToNextDay">
|
||||
<text class="nav-text">下一天</text>
|
||||
<!-- <text class="nav-icon">›</text> -->
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 温度趋势图表 -->
|
||||
@ -101,10 +106,12 @@ export default {
|
||||
},
|
||||
// 页面初始化状态
|
||||
hasInitialized: false,
|
||||
// 查询模式:'default' 表示过去24小时,'date' 表示按日期查询
|
||||
queryMode: 'default',
|
||||
// ECharts配置选项
|
||||
temperatureOption: {
|
||||
title: {
|
||||
text: '温度趋势',
|
||||
text: '',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
@ -115,7 +122,7 @@ export default {
|
||||
trigger: 'axis',
|
||||
formatter: function(params) {
|
||||
const data = params[0];
|
||||
return `时间: ${data.axisValue}<br/>温度: ${data.value}°C`;
|
||||
return `时间: ${data.axisValue} 温度: ${data.value}°C`;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
@ -126,7 +133,7 @@ export default {
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: Array.from({length: 24}, (_, i) => `${i.toString().padStart(2, '0')}:00`),
|
||||
data: this.generateXAxisLabels(),
|
||||
axisLabel: {
|
||||
interval: 3, // 每4个小时显示一个标签
|
||||
fontSize: 10
|
||||
@ -172,7 +179,7 @@ export default {
|
||||
},
|
||||
humidityOption: {
|
||||
title: {
|
||||
text: '湿度趋势',
|
||||
text: '',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
@ -183,7 +190,7 @@ export default {
|
||||
trigger: 'axis',
|
||||
formatter: function(params) {
|
||||
const data = params[0];
|
||||
return `时间: ${data.axisValue}<br/>湿度: ${data.value}%`;
|
||||
return `时间: ${data.axisValue} 湿度: ${data.value}%`;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
@ -194,7 +201,7 @@ export default {
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: Array.from({length: 24}, (_, i) => `${i.toString().padStart(2, '0')}:00`),
|
||||
data: this.generateXAxisLabels(),
|
||||
axisLabel: {
|
||||
interval: 3,
|
||||
fontSize: 10
|
||||
@ -240,7 +247,7 @@ export default {
|
||||
},
|
||||
pm25Option: {
|
||||
title: {
|
||||
text: 'PM2.5趋势',
|
||||
text: '',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
@ -251,7 +258,7 @@ export default {
|
||||
trigger: 'axis',
|
||||
formatter: function(params) {
|
||||
const data = params[0];
|
||||
return `时间: ${data.axisValue}<br/>PM2.5: ${data.value}μg/m³`;
|
||||
return `时间: ${data.axisValue} PM2.5: ${data.value}μg/m³`;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
@ -262,7 +269,7 @@ export default {
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: Array.from({length: 24}, (_, i) => `${i.toString().padStart(2, '0')}:00`),
|
||||
data: this.generateXAxisLabels(),
|
||||
axisLabel: {
|
||||
interval: 3,
|
||||
fontSize: 10
|
||||
@ -334,19 +341,90 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 生成x轴标签
|
||||
generateXAxisLabels() {
|
||||
if (this.queryMode === 'date') {
|
||||
// 按日期查询时,显示0-23小时
|
||||
return Array.from({length: 24}, (_, i) => `${i.toString().padStart(2, '0')}:00`)
|
||||
} else {
|
||||
// 默认查询时,显示当前时间之前24小时(整点时间)
|
||||
const now = new Date()
|
||||
const currentHour = now.getHours()
|
||||
const labels = []
|
||||
for (let i = 23; i >= 0; i--) {
|
||||
// 计算目标小时
|
||||
let targetHour = currentHour - i
|
||||
if (targetHour < 0) {
|
||||
targetHour += 24 // 跨天处理
|
||||
}
|
||||
labels.push(`${String(targetHour).padStart(2, '0')}:00`)
|
||||
}
|
||||
|
||||
return labels
|
||||
}
|
||||
},
|
||||
|
||||
// 获取今天的日期(本地时区)
|
||||
getTodayDate() {
|
||||
const today = new Date()
|
||||
const year = today.getFullYear()
|
||||
const month = String(today.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(today.getDate()).padStart(2, '0')
|
||||
|
||||
console.log('📅 获取今天日期:', {
|
||||
'原始Date对象': today,
|
||||
'ISO字符串': today.toISOString(),
|
||||
'本地字符串': today.toLocaleString(),
|
||||
'本地日期字符串': today.toLocaleDateString(),
|
||||
'本地时间字符串': today.toLocaleTimeString(),
|
||||
'时区偏移': today.getTimezoneOffset(),
|
||||
'格式化结果': `${year}-${month}-${day}`
|
||||
})
|
||||
|
||||
return `${year}-${month}-${day}`
|
||||
},
|
||||
|
||||
// 格式化日期为 YYYY-MM-DD 格式
|
||||
formatDate(date) {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
},
|
||||
|
||||
// 获取星期几
|
||||
getWeekday(dateString) {
|
||||
const date = new Date(dateString)
|
||||
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||
return weekdays[date.getDay()]
|
||||
},
|
||||
|
||||
|
||||
// 获取时间范围显示文本
|
||||
getTimeRangeText() {
|
||||
if (this.queryMode === 'date') {
|
||||
return `查询日期: ${this.selectedDate}`
|
||||
} else {
|
||||
const now = new Date()
|
||||
const past24Hours = new Date(now.getTime() - 24 * 60 * 60 * 1000)
|
||||
const startTime = this.formatTimeDisplay(past24Hours)
|
||||
const endTime = this.formatTimeDisplay(now)
|
||||
return `过去24小时: ${startTime} ~ ${endTime}`
|
||||
}
|
||||
},
|
||||
|
||||
// 格式化时间显示(用于界面显示)
|
||||
formatTimeDisplay(date) {
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${month}-${day} ${hours}:${minutes}`
|
||||
},
|
||||
// 初始化MQTT监听
|
||||
initMqttListener() {
|
||||
// 监听数据更新
|
||||
this.dataUpdateHandler = (data) => {
|
||||
console.log('参数记录页面收到MQTT数据:', data)
|
||||
// this.updateChartData(data)
|
||||
}
|
||||
mqttDataManager.addListener('dataUpdate', this.dataUpdateHandler)
|
||||
@ -354,7 +432,6 @@ export default {
|
||||
// 监听连接状态
|
||||
this.statusUpdateHandler = (status) => {
|
||||
this.connectionStatus = status
|
||||
console.log('参数记录页面连接状态更新:', status)
|
||||
}
|
||||
mqttDataManager.addListener('connectionStatus', this.statusUpdateHandler)
|
||||
|
||||
@ -364,25 +441,33 @@ export default {
|
||||
|
||||
// 更新图表数据
|
||||
updateChartData(data) {
|
||||
console.log('📊 参数记录页面更新数据:', data)
|
||||
|
||||
// 只处理WSD设备的数据
|
||||
if (data.deviceType === 'WSD') {
|
||||
const now = new Date()
|
||||
const currentHour = now.getHours()
|
||||
const currentMinute = now.getMinutes()
|
||||
const currentTimeInHours = currentHour + currentMinute / 60
|
||||
|
||||
// 更新对应小时的数据
|
||||
// 计算数据在数组中的索引位置
|
||||
let dataIndex
|
||||
if (this.queryMode === 'date') {
|
||||
// 按日期查询时,直接使用当前小时
|
||||
dataIndex = currentHour
|
||||
} else {
|
||||
// 默认查询时,当前时间对应索引0(最新数据)
|
||||
dataIndex = 0
|
||||
}
|
||||
|
||||
// 更新对应位置的数据
|
||||
if (data.temperature !== undefined) {
|
||||
Math.round(data.temperature) && (this.temperatureData[currentHour] = Math.round(data.temperature))
|
||||
console.log(`✅ 温度数据已更新 - 小时${currentHour}:`, this.temperatureData[currentHour])
|
||||
Math.round(data.temperature) && (this.temperatureData[dataIndex] = Math.round(data.temperature))
|
||||
}
|
||||
if (data.humidity !== undefined) {
|
||||
Math.round(data.humidity) && (this.humidityData[currentHour] = Math.round(data.humidity))
|
||||
console.log(`✅ 湿度数据已更新 - 小时${currentHour}:`, this.humidityData[currentHour])
|
||||
Math.round(data.humidity) && (this.humidityData[dataIndex] = Math.round(data.humidity))
|
||||
}
|
||||
if (data.pm !== undefined) {
|
||||
Math.round(data.pm) && (this.pm25Data[currentHour] = Math.round(data.pm))
|
||||
console.log(`✅ PM2.5数据已更新 - 小时${currentHour}:`, this.pm25Data[currentHour])
|
||||
Math.round(data.pm) && (this.pm25Data[dataIndex] = Math.round(data.pm))
|
||||
}
|
||||
|
||||
// 重新绘制图表
|
||||
@ -391,9 +476,9 @@ export default {
|
||||
})
|
||||
|
||||
console.log('✅ 图表数据更新完成:', {
|
||||
temperature: this.temperatureData[currentHour],
|
||||
humidity: this.humidityData[currentHour],
|
||||
hour: currentHour
|
||||
temperature: this.temperatureData[dataIndex],
|
||||
humidity: this.humidityData[dataIndex],
|
||||
dataIndex: dataIndex
|
||||
})
|
||||
} else {
|
||||
console.log('⚠️ 非WSD设备数据,跳过图表更新:', data.deviceType)
|
||||
@ -402,26 +487,29 @@ export default {
|
||||
|
||||
// 图表初始化方法
|
||||
initTemperatureChart() {
|
||||
console.log('初始化温度图表')
|
||||
this.temperatureOption.xAxis.data = this.generateXAxisLabels()
|
||||
this.temperatureOption.series[0].data = this.temperatureData
|
||||
this.$refs.temperatureChartRef.init(this.temperatureOption)
|
||||
},
|
||||
|
||||
initHumidityChart() {
|
||||
console.log('初始化湿度图表')
|
||||
this.humidityOption.xAxis.data = this.generateXAxisLabels()
|
||||
this.humidityOption.series[0].data = this.humidityData
|
||||
this.$refs.humidityChartRef.init(this.humidityOption)
|
||||
},
|
||||
|
||||
initPM25Chart() {
|
||||
console.log('初始化PM2.5图表')
|
||||
this.pm25Option.xAxis.data = this.generateXAxisLabels()
|
||||
this.pm25Option.series[0].data = this.pm25Data
|
||||
this.$refs.pm25ChartRef.init(this.pm25Option)
|
||||
},
|
||||
|
||||
onDateChange(e) {
|
||||
this.selectedDate = e.detail.value
|
||||
console.log('📅 日期已更改为:', this.selectedDate)
|
||||
// 上一天
|
||||
goToPreviousDay() {
|
||||
const currentDate = new Date(this.selectedDate)
|
||||
currentDate.setDate(currentDate.getDate() - 1)
|
||||
this.selectedDate = this.formatDate(currentDate)
|
||||
this.queryMode = 'date' // 切换到按日期查询模式
|
||||
|
||||
// 显示加载状态
|
||||
uni.showLoading({
|
||||
@ -429,29 +517,64 @@ export default {
|
||||
})
|
||||
|
||||
// 重新获取历史数据
|
||||
this.getHistoryData().finally(() => {
|
||||
this.getHistoryDataByDate().finally(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
},
|
||||
|
||||
// 下一天
|
||||
goToNextDay() {
|
||||
const currentDate = new Date(this.selectedDate)
|
||||
const today = new Date()
|
||||
|
||||
// 检查是否已经是今天,如果是则不允许继续往后
|
||||
if (this.selectedDate >= this.formatDate(today)) {
|
||||
uni.showToast({
|
||||
title: '不能查看未来日期',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
currentDate.setDate(currentDate.getDate() + 1)
|
||||
this.selectedDate = this.formatDate(currentDate)
|
||||
this.queryMode = 'date' // 切换到按日期查询模式
|
||||
|
||||
// 显示加载状态
|
||||
uni.showLoading({
|
||||
title: '加载数据中...'
|
||||
})
|
||||
|
||||
// 重新获取历史数据
|
||||
this.getHistoryDataByDate().finally(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
},
|
||||
|
||||
// 更新图表数据
|
||||
updateCharts() {
|
||||
const xAxisLabels = this.generateXAxisLabels()
|
||||
|
||||
if (this.$refs.temperatureChartRef) {
|
||||
this.temperatureOption.xAxis.data = xAxisLabels
|
||||
this.temperatureOption.series[0].data = this.temperatureData
|
||||
this.$refs.temperatureChartRef.setOption(this.temperatureOption)
|
||||
}
|
||||
if (this.$refs.humidityChartRef) {
|
||||
this.humidityOption.xAxis.data = xAxisLabels
|
||||
this.humidityOption.series[0].data = this.humidityData
|
||||
this.$refs.humidityChartRef.setOption(this.humidityOption)
|
||||
}
|
||||
if (this.$refs.pm25ChartRef) {
|
||||
this.pm25Option.xAxis.data = xAxisLabels
|
||||
this.pm25Option.series[0].data = this.pm25Data
|
||||
this.$refs.pm25ChartRef.setOption(this.pm25Option)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取历史数据
|
||||
async getHistoryData() {
|
||||
// 根据选择的日期获取历史数据
|
||||
async getHistoryDataByDate() {
|
||||
try {
|
||||
// 根据选择的日期构建时间范围
|
||||
const startTime = `${this.selectedDate} 00:00:00`
|
||||
@ -462,7 +585,83 @@ export default {
|
||||
endTime: endTime
|
||||
}
|
||||
|
||||
console.log('📊 请求历史数据:', params)
|
||||
const response = await dataHistoryApi.getHistory(params)
|
||||
|
||||
// 处理历史数据
|
||||
if (response && Array.isArray(response) && response.length > 0) {
|
||||
this.historyData = response
|
||||
this.processHistoryData(response)
|
||||
|
||||
// 更新数据状态
|
||||
this.dataStatus = {
|
||||
isRealData: true,
|
||||
lastUpdateTime: new Date().toLocaleString(),
|
||||
dataSource: 'api'
|
||||
}
|
||||
|
||||
// 保存查询事件
|
||||
await this.createQueryEvent('success', response.length)
|
||||
} else {
|
||||
console.log('📊 没有历史数据,显示空状态')
|
||||
// 没有数据时显示空状态
|
||||
this.showEmptyState()
|
||||
|
||||
// 更新数据状态
|
||||
this.dataStatus = {
|
||||
isRealData: false,
|
||||
lastUpdateTime: new Date().toLocaleString(),
|
||||
dataSource: 'empty'
|
||||
}
|
||||
|
||||
// 保存查询事件(无数据)
|
||||
await this.createQueryEvent('empty', 0)
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 历史数据获取失败:', error)
|
||||
// 出错时显示空状态
|
||||
this.showEmptyState()
|
||||
|
||||
// 更新数据状态
|
||||
this.dataStatus = {
|
||||
isRealData: false,
|
||||
lastUpdateTime: new Date().toLocaleString(),
|
||||
dataSource: 'error'
|
||||
}
|
||||
|
||||
// 保存查询事件(错误)
|
||||
await this.createQueryEvent('error', 0)
|
||||
|
||||
// 显示错误提示
|
||||
uni.showToast({
|
||||
title: '数据加载失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
// 获取历史数据(默认过去24小时)
|
||||
async getHistoryData() {
|
||||
try {
|
||||
// 构建时间范围:从当前时间开始过去24小时
|
||||
const now = new Date()
|
||||
const past24Hours = new Date(now.getTime() - 24 * 60 * 60 * 1000) // 24小时前
|
||||
|
||||
// 格式化时间字符串
|
||||
const startTime = this.formatDateTimeString(past24Hours)
|
||||
const endTime = this.formatDateTimeString(now)
|
||||
|
||||
const params = {
|
||||
startTime: startTime,
|
||||
endTime: endTime
|
||||
}
|
||||
|
||||
console.log('📊 请求历史数据(过去24小时):', params)
|
||||
|
||||
const response = await dataHistoryApi.getHistory(params)
|
||||
|
||||
@ -586,11 +785,37 @@ export default {
|
||||
const hour = time.getHours()
|
||||
const minute = time.getMinutes()
|
||||
|
||||
// 计算在24小时数组中的索引位置
|
||||
let dataIndex
|
||||
if (this.queryMode === 'date') {
|
||||
// 按日期查询时,直接使用小时作为索引
|
||||
dataIndex = hour
|
||||
} else {
|
||||
// 默认查询时,需要计算相对于当前时间的位置(按小时计算)
|
||||
const now = new Date()
|
||||
const currentHour = now.getHours()
|
||||
|
||||
// 计算时间差(小时)
|
||||
let timeDiff = currentHour - hour
|
||||
if (timeDiff < 0) {
|
||||
timeDiff += 24 // 跨天的情况
|
||||
}
|
||||
|
||||
// 转换为数组索引(23表示24小时前,0表示当前时间)
|
||||
dataIndex = 23 - timeDiff
|
||||
|
||||
|
||||
if (dataIndex < 0 || dataIndex >= 24) {
|
||||
console.log(`⚠️ 数据超出范围,跳过 - 索引:${dataIndex}`)
|
||||
return // 超出范围的数据跳过
|
||||
}
|
||||
}
|
||||
|
||||
// 处理温度数据 (wd是温度)
|
||||
const temperature = item.wd || item.temperature || item.temp || item.T
|
||||
if (temperature !== undefined && temperature !== null && temperature >= 0) {
|
||||
this.chartData.temperature.push({
|
||||
time: hour + minute / 60,
|
||||
time: dataIndex,
|
||||
value: Number(temperature),
|
||||
timestamp: item.createTime || item.timestamp || item.time
|
||||
})
|
||||
@ -600,7 +825,7 @@ export default {
|
||||
const humidity = item.sd || item.humidity || item.hum || item.H
|
||||
if (humidity !== undefined && humidity !== null && humidity >= 0) {
|
||||
this.chartData.humidity.push({
|
||||
time: hour + minute / 60,
|
||||
time: dataIndex,
|
||||
value: Number(humidity),
|
||||
timestamp: item.createTime || item.timestamp || item.time
|
||||
})
|
||||
@ -610,7 +835,7 @@ export default {
|
||||
const pm = item.pm || item.pm25 || item.pm2_5 || item.PM
|
||||
if (pm !== undefined && pm !== null && pm >= 0) {
|
||||
this.chartData.pm.push({
|
||||
time: hour + minute / 60,
|
||||
time: dataIndex,
|
||||
value: Number(pm),
|
||||
timestamp: item.createTime || item.timestamp || item.time
|
||||
})
|
||||
@ -633,17 +858,20 @@ export default {
|
||||
updateChartsWithHistoryData() {
|
||||
console.log('🎨 使用历史数据更新图表')
|
||||
|
||||
const xAxisLabels = this.generateXAxisLabels()
|
||||
|
||||
// 处理温度数据
|
||||
if (this.chartData.temperature.length > 0) {
|
||||
const temperatureData = new Array(24).fill(0)
|
||||
this.chartData.temperature.forEach(item => {
|
||||
const hour = Math.floor(item.time)
|
||||
if (hour >= 0 && hour < 24) {
|
||||
temperatureData[hour] = item.value || 0
|
||||
const dataIndex = Math.floor(item.time)
|
||||
if (dataIndex >= 0 && dataIndex < 24) {
|
||||
temperatureData[dataIndex] = item.value || 0
|
||||
}
|
||||
})
|
||||
this.temperatureData = temperatureData
|
||||
if (this.$refs.temperatureChartRef) {
|
||||
this.temperatureOption.xAxis.data = xAxisLabels
|
||||
this.temperatureOption.series[0].data = temperatureData
|
||||
this.$refs.temperatureChartRef.setOption(this.temperatureOption)
|
||||
}
|
||||
@ -651,6 +879,7 @@ export default {
|
||||
// 没有温度数据时,使用0填充
|
||||
this.temperatureData = new Array(24).fill(0)
|
||||
if (this.$refs.temperatureChartRef) {
|
||||
this.temperatureOption.xAxis.data = xAxisLabels
|
||||
this.temperatureOption.series[0].data = this.temperatureData
|
||||
this.$refs.temperatureChartRef.setOption(this.temperatureOption)
|
||||
}
|
||||
@ -660,13 +889,14 @@ export default {
|
||||
if (this.chartData.humidity.length > 0) {
|
||||
const humidityData = new Array(24).fill(0)
|
||||
this.chartData.humidity.forEach(item => {
|
||||
const hour = Math.floor(item.time)
|
||||
if (hour >= 0 && hour < 24) {
|
||||
humidityData[hour] = item.value || 0
|
||||
const dataIndex = Math.floor(item.time)
|
||||
if (dataIndex >= 0 && dataIndex < 24) {
|
||||
humidityData[dataIndex] = item.value || 0
|
||||
}
|
||||
})
|
||||
this.humidityData = humidityData
|
||||
if (this.$refs.humidityChartRef) {
|
||||
this.humidityOption.xAxis.data = xAxisLabels
|
||||
this.humidityOption.series[0].data = humidityData
|
||||
this.$refs.humidityChartRef.setOption(this.humidityOption)
|
||||
}
|
||||
@ -674,6 +904,7 @@ export default {
|
||||
// 没有湿度数据时,使用0填充
|
||||
this.humidityData = new Array(24).fill(0)
|
||||
if (this.$refs.humidityChartRef) {
|
||||
this.humidityOption.xAxis.data = xAxisLabels
|
||||
this.humidityOption.series[0].data = this.humidityData
|
||||
this.$refs.humidityChartRef.setOption(this.humidityOption)
|
||||
}
|
||||
@ -683,13 +914,14 @@ export default {
|
||||
if (this.chartData.pm.length > 0) {
|
||||
const pmData = new Array(24).fill(0)
|
||||
this.chartData.pm.forEach(item => {
|
||||
const hour = Math.floor(item.time)
|
||||
if (hour >= 0 && hour < 24) {
|
||||
pmData[hour] = item.value || 0
|
||||
const dataIndex = Math.floor(item.time)
|
||||
if (dataIndex >= 0 && dataIndex < 24) {
|
||||
pmData[dataIndex] = item.value || 0
|
||||
}
|
||||
})
|
||||
this.pm25Data = pmData
|
||||
if (this.$refs.pm25ChartRef) {
|
||||
this.pm25Option.xAxis.data = xAxisLabels
|
||||
this.pm25Option.series[0].data = pmData
|
||||
this.$refs.pm25ChartRef.setOption(this.pm25Option)
|
||||
}
|
||||
@ -697,6 +929,7 @@ export default {
|
||||
// 没有PM数据时,使用0填充
|
||||
this.pm25Data = new Array(24).fill(0)
|
||||
if (this.$refs.pm25ChartRef) {
|
||||
this.pm25Option.xAxis.data = xAxisLabels
|
||||
this.pm25Option.series[0].data = this.pm25Data
|
||||
this.$refs.pm25ChartRef.setOption(this.pm25Option)
|
||||
}
|
||||
@ -752,6 +985,19 @@ export default {
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
|
||||
const result = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
return result
|
||||
},
|
||||
|
||||
// 格式化时间字符串(用于API请求)
|
||||
formatDateTimeString(date) {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
}
|
||||
@ -768,12 +1014,20 @@ export default {
|
||||
|
||||
.date-selector {
|
||||
background: white;
|
||||
border-radius: 8rpx;
|
||||
padding: 15rpx;
|
||||
margin-bottom: 15rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.time-range-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.time-range-text {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.connection-status {
|
||||
@ -809,23 +1063,70 @@ export default {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
.date-navigation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 15rpx 20rpx;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 6rpx;
|
||||
gap: 15rpx;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
// padding: 12rpx 20rpx;
|
||||
// background: #007aff;
|
||||
// color: white;
|
||||
margin: 0;
|
||||
border: none;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
// min-width: 120rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6rpx;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-button:active {
|
||||
// background: #0056b3;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.nav-button::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 24rpx;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
// color: white;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.current-date {
|
||||
padding: 16rpx 24rpx;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8rpx;
|
||||
min-width: 180rpx;
|
||||
text-align: center;
|
||||
border: 1rpx solid #e9ecef;
|
||||
}
|
||||
|
||||
.date-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.picker-arrow {
|
||||
color: #999;
|
||||
font-size: 20rpx;
|
||||
.date-weekday {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.chart-card {
|
||||
@ -839,7 +1140,7 @@ export default {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
// margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
|
||||
@ -1,22 +1,86 @@
|
||||
<template>
|
||||
<view class="visual-monitoring-page">
|
||||
<!-- 固定头部 -->
|
||||
<!-- 固定头部 - 有视频时隐藏 -->
|
||||
<view class="fixed-header">
|
||||
<text class="header-title">移动式检修车间</text>
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view class="tabbar-content">
|
||||
<!-- <demo /> -->
|
||||
<view class="no-data-container" v-if="!ezstate">
|
||||
<view class="no-data-icon">📹</view>
|
||||
<text class="no-data-text">暂无监控数据</text>
|
||||
</view>
|
||||
|
||||
<!-- 视频播放区域 - 保持16:9比例 -->
|
||||
<view v-else-if="ezstate" :key="videoData" class="video-wrapper">
|
||||
<view class="video-content">
|
||||
<!-- 使用简化版播放器 -->
|
||||
<EzvizVideoPlayer
|
||||
ref="playerVideoRef"
|
||||
:show-debug="debugMode"
|
||||
@playStateChange="handlePlayStateChange"
|
||||
></EzvizVideoPlayer>
|
||||
</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测试按钮 -->
|
||||
<view class="test-section" v-if="!ezstate">
|
||||
<button @click="checkDevice" class="test-btn">检查设备状态</button>
|
||||
<button @click="toggleDebug" class="test-btn">
|
||||
{{ debugMode ? '关闭调试' : '开启调试' }}
|
||||
</button>
|
||||
<button @click="getVideoData" class="test-btn">启动视频播放</button>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 改用简化版播放器
|
||||
import EzvizVideoPlayer from '@/components/EzvizVideoPlayerSimple.vue'
|
||||
import tokenManager from '@/utils/ezvizTokenManager.js'
|
||||
import deviceChecker from '@/utils/ezvizDeviceChecker.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EzvizVideoPlayer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ezstate:false,
|
||||
debugMode: true, // 默认开启调试模式
|
||||
videoLoaded: false,
|
||||
isRecording: false,
|
||||
isPlaying: true, // 播放状态
|
||||
cameraStatus: {
|
||||
text: '离线',
|
||||
class: 'offline'
|
||||
@ -51,105 +115,187 @@ export default {
|
||||
},
|
||||
onLoad() {
|
||||
console.log('视觉监控页面加载')
|
||||
this.getVideoData()
|
||||
|
||||
},
|
||||
onShow() {
|
||||
console.log('📱 视觉监控页面显示,触发页面更新')
|
||||
// 可以在这里添加重新连接摄像头等逻辑
|
||||
this.getVideoData()
|
||||
},
|
||||
methods: {
|
||||
connectCamera() {
|
||||
// 切换调试模式
|
||||
toggleDebug() {
|
||||
this.debugMode = !this.debugMode
|
||||
console.log('调试模式:', this.debugMode ? '开启' : '关闭')
|
||||
|
||||
uni.showToast({
|
||||
title: this.debugMode ? '调试模式已开启' : '调试模式已关闭',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
},
|
||||
|
||||
// 初始化播放器
|
||||
async handleInitPlayer() {
|
||||
console.log('🔄 重新初始化播放器...')
|
||||
|
||||
uni.showLoading({
|
||||
title: '连接中...'
|
||||
title: '正在初始化...'
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// 重新获取视频数据并初始化
|
||||
await this.getVideoData()
|
||||
|
||||
uni.hideLoading()
|
||||
this.videoLoaded = true
|
||||
this.cameraStatus = {
|
||||
text: '在线',
|
||||
class: 'online'
|
||||
}
|
||||
uni.showToast({
|
||||
title: '摄像头连接成功',
|
||||
icon: 'success'
|
||||
title: '初始化成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
|
||||
// 重置播放状态
|
||||
this.isPlaying = true
|
||||
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
console.error('初始化失败:', error)
|
||||
uni.showToast({
|
||||
title: '初始化失败',
|
||||
icon: 'error',
|
||||
duration: 2000
|
||||
})
|
||||
}, 2000)
|
||||
},
|
||||
toggleRecording() {
|
||||
this.isRecording = !this.isRecording
|
||||
this.recordingStatus = {
|
||||
text: this.isRecording ? '录制中' : '未录制',
|
||||
class: this.isRecording ? 'recording' : 'inactive'
|
||||
}
|
||||
},
|
||||
|
||||
// 切换播放/暂停
|
||||
handleTogglePlay() {
|
||||
console.log('🎬 切换播放状态:', this.isPlaying ? '暂停' : '播放')
|
||||
|
||||
uni.showToast({
|
||||
title: this.isRecording ? '开始录制' : '停止录制',
|
||||
icon: 'success'
|
||||
if (this.$refs.playerVideoRef) {
|
||||
// 调用播放器组件的切换播放方法(状态会通过事件同步)
|
||||
this.$refs.playerVideoRef.togglePlay()
|
||||
} else {
|
||||
console.error('❌ 播放器组件未找到')
|
||||
uni.showToast({
|
||||
title: '播放器未就绪',
|
||||
icon: 'error',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 处理播放状态变化(由播放器组件触发)
|
||||
handlePlayStateChange(isPlaying) {
|
||||
console.log('📡 播放状态变化:', isPlaying ? '播放中' : '已暂停')
|
||||
this.isPlaying = isPlaying
|
||||
},
|
||||
|
||||
// 检查设备状态
|
||||
async checkDevice() {
|
||||
console.log('🔍 开始检查设备状态...')
|
||||
|
||||
const playUrl = "ezopen://open.ys7.com/FT1718031/1.hd.live"
|
||||
|
||||
uni.showLoading({
|
||||
title: '检查设备中...'
|
||||
})
|
||||
},
|
||||
takeSnapshot() {
|
||||
uni.showToast({
|
||||
title: '拍照成功',
|
||||
icon: 'success'
|
||||
})
|
||||
},
|
||||
toggleFullscreen() {
|
||||
uni.showToast({
|
||||
title: '全屏功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
onQualityChange(e) {
|
||||
this.qualityIndex = e.detail.value
|
||||
},
|
||||
onDurationChange(e) {
|
||||
this.durationIndex = e.detail.value
|
||||
},
|
||||
onAutoSaveChange(e) {
|
||||
this.autoSave = e.detail.value
|
||||
},
|
||||
playVideo(item) {
|
||||
uni.showToast({
|
||||
title: `播放 ${item.time}`,
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
downloadVideo(item) {
|
||||
uni.showToast({
|
||||
title: `下载 ${item.time}`,
|
||||
icon: 'success'
|
||||
})
|
||||
},
|
||||
deleteVideo(item) {
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: `确定要删除 ${item.time} 的录制文件吗?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
const index = this.historyList.indexOf(item)
|
||||
this.historyList.splice(index, 1)
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await deviceChecker.comprehensiveCheck(playUrl)
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
if (result.success) {
|
||||
const status = result.isOnline ? '在线' : '离线'
|
||||
const message = `设备 ${result.deviceSerial}: ${status}\n设备名: ${result.device.deviceName || '未知'}`
|
||||
|
||||
uni.showModal({
|
||||
title: '设备检查结果',
|
||||
content: message,
|
||||
showCancel: false
|
||||
})
|
||||
|
||||
console.log('✅ 设备检查结果:', result)
|
||||
} else {
|
||||
uni.showModal({
|
||||
title: '设备检查失败',
|
||||
content: result.error,
|
||||
showCancel: false
|
||||
})
|
||||
console.error('❌ 设备检查失败:', result.error)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '检查异常',
|
||||
icon: 'error',
|
||||
duration: 3000
|
||||
})
|
||||
console.error('设备检查异常:', error)
|
||||
}
|
||||
},
|
||||
clearHistory() {
|
||||
uni.showModal({
|
||||
title: '确认清空',
|
||||
content: '确定要清空所有录制历史吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.historyList = []
|
||||
uni.showToast({
|
||||
title: '清空成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
async getVideoData() {
|
||||
console.log('getVideoData')
|
||||
|
||||
try {
|
||||
let ezuikitInfo = {}
|
||||
|
||||
// 使用TokenManager自动获取AccessToken
|
||||
try {
|
||||
console.log('🔑 开始获取AccessToken...')
|
||||
const accessToken = await tokenManager.getValidAccessToken()
|
||||
ezuikitInfo = {
|
||||
accessToken: accessToken,
|
||||
play_url: "ezopen://open.ys7.com/FT1718031/1.hd.live"
|
||||
|
||||
}
|
||||
console.log('✅ 使用自动获取的AccessToken:', accessToken.substring(0, 20) + '...')
|
||||
} catch (error) {
|
||||
console.error('❌ 自动获取AccessToken失败:', error)
|
||||
|
||||
// 如果自动获取失败,使用备用token(需要手动更新)
|
||||
console.log('🔄 使用备用AccessToken')
|
||||
ezuikitInfo = {
|
||||
accessToken: "at.4q22023n62a4knwpcx1yxavda1sfqfo5-3ns0ca16sb-1wgwwc3-aj2mctqys",
|
||||
play_url: "ezopen://open.ys7.com/FT1718031/1.hd.live"
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: 'AccessToken自动获取失败,使用备用token',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 先启用视频状态,让组件渲染
|
||||
this.ezstate = true
|
||||
|
||||
// 等待组件渲染完成后初始化播放器
|
||||
await this.$nextTick()
|
||||
|
||||
// 确保ref存在后再调用
|
||||
if (this.$refs.playerVideoRef) {
|
||||
this.$refs.playerVideoRef.initEzuikit(ezuikitInfo)
|
||||
} else {
|
||||
console.error('❌ 播放器组件未找到')
|
||||
uni.showToast({
|
||||
title: '播放器组件加载失败',
|
||||
icon: 'error',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('初始化视频失败:', error)
|
||||
uni.showToast({
|
||||
title: '视频初始化失败',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -157,10 +303,180 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.visual-monitoring-page {
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f5f6fa;
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.tabbar-content {
|
||||
width: 100%;
|
||||
height: calc(100vh - 100rpx); /* 减去底部tabbar */
|
||||
padding: 30rpx;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 视频外层容器 */
|
||||
.video-wrapper {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
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;
|
||||
background-color: #0056b3;
|
||||
|
||||
/* 使用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;
|
||||
}
|
||||
}
|
||||
|
||||
/* 视频控制按钮区域 */
|
||||
.control-section {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 0 10rpx;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.init-btn {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.init-btn:active {
|
||||
background: linear-gradient(135deg, #5568d3 0%, #6a4193 100%);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.pause-btn {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
}
|
||||
|
||||
.pause-btn:active {
|
||||
background: linear-gradient(135deg, #e082ea 0%, #e4465b 100%);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.test-section {
|
||||
padding: 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.test-btn {
|
||||
background: #007aff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx 40rpx;
|
||||
font-size: 28rpx;
|
||||
width: 300rpx;
|
||||
}
|
||||
|
||||
.test-btn:active {
|
||||
background: #0056b3;
|
||||
}
|
||||
|
||||
.camera-status {
|
||||
|
||||
130
src/static/html/ezviz-iframe.html
Normal file
130
src/static/html/ezviz-iframe.html
Normal file
@ -0,0 +1,130 @@
|
||||
<!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: 50%;
|
||||
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>
|
||||
|
||||
@ -88,7 +88,7 @@
|
||||
// 统一的页面内容区域样式
|
||||
.page-content {
|
||||
flex: 1;
|
||||
padding: 24rpx 0;
|
||||
// padding: 24rpx 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -329,7 +329,7 @@
|
||||
}
|
||||
|
||||
.page-content {
|
||||
padding: 28rpx 0;
|
||||
// padding: 28rpx 0;
|
||||
}
|
||||
|
||||
.header-cell {
|
||||
@ -360,7 +360,7 @@
|
||||
}
|
||||
|
||||
.page-content {
|
||||
padding: 20rpx 0;
|
||||
// padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.header-cell {
|
||||
@ -401,7 +401,7 @@
|
||||
}
|
||||
|
||||
.page-content {
|
||||
padding: 32rpx 0;
|
||||
// padding: 32rpx 0;
|
||||
}
|
||||
|
||||
.header-cell {
|
||||
|
||||
121
src/utils/api.js
121
src/utils/api.js
@ -13,101 +13,16 @@ export const dataHistoryApi = {
|
||||
}
|
||||
}
|
||||
|
||||
// 设备相关接口
|
||||
export const deviceApi = {
|
||||
// 获取设备详情
|
||||
getDetail(deviceId) {
|
||||
return httpService.get(`/api/devices/${deviceId}`)
|
||||
},
|
||||
|
||||
// 获取设备列表
|
||||
getList(params = {}) {
|
||||
return httpService.get('/api/devices', params)
|
||||
},
|
||||
|
||||
// 更新设备状态
|
||||
updateStatus(deviceId, status) {
|
||||
return httpService.put(`/api/devices/${deviceId}/status`, { status })
|
||||
}
|
||||
}
|
||||
|
||||
// 环境参数接口
|
||||
export const environmentApi = {
|
||||
// 获取环境参数
|
||||
getParams(params = {}) {
|
||||
return httpService.get('/api/environment/params', params)
|
||||
},
|
||||
|
||||
// 获取环境参数历史
|
||||
getHistory(params) {
|
||||
return httpService.post('/api/environment/history', params)
|
||||
}
|
||||
}
|
||||
|
||||
// 报警相关接口
|
||||
export const alarmApi = {
|
||||
// 获取报警记录
|
||||
getRecords(params = {}) {
|
||||
return httpService.get('/api/alarms', params)
|
||||
},
|
||||
|
||||
// 获取报警统计
|
||||
getStatistics(params = {}) {
|
||||
return httpService.get('/api/alarms/statistics', params)
|
||||
},
|
||||
|
||||
// 处理报警
|
||||
handleAlarm(alarmId, action) {
|
||||
return httpService.put(`/api/alarms/${alarmId}/handle`, { action })
|
||||
}
|
||||
}
|
||||
|
||||
// 系统日志接口
|
||||
export const logApi = {
|
||||
// 获取系统日志
|
||||
getLogs(params = {}) {
|
||||
return httpService.get('/api/logs', params)
|
||||
},
|
||||
|
||||
// 获取日志统计
|
||||
getStatistics(params = {}) {
|
||||
return httpService.get('/api/logs/statistics', params)
|
||||
}
|
||||
}
|
||||
|
||||
// 用户相关接口
|
||||
export const userApi = {
|
||||
// 用户登录
|
||||
login(credentials) {
|
||||
return httpService.post('/api/auth/login', credentials)
|
||||
},
|
||||
|
||||
// 用户登出
|
||||
logout() {
|
||||
return httpService.post('/api/auth/logout')
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
getUserInfo() {
|
||||
return httpService.get('/api/user/info')
|
||||
},
|
||||
|
||||
// 更新用户信息
|
||||
updateUserInfo(userInfo) {
|
||||
return httpService.put('/api/user/info', userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// 温湿度数据接口
|
||||
// 空调目标温湿度数据接口
|
||||
export const thDataApi = {
|
||||
// 获取最新空调温度
|
||||
getLatest() {
|
||||
return httpService.get('/api/th/data/latest')
|
||||
return httpService.get('/api/ac/data/latest')
|
||||
},
|
||||
|
||||
// 提交温湿度数据
|
||||
submit(data) {
|
||||
return httpService.post('/api/th/data', data)
|
||||
return httpService.post('/api/ac/data', data)
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,15 +52,21 @@ export const eventApi = {
|
||||
}
|
||||
}
|
||||
|
||||
// 导出所有API
|
||||
export default {
|
||||
dataHistory: dataHistoryApi,
|
||||
device: deviceApi,
|
||||
environment: environmentApi,
|
||||
alarm: alarmApi,
|
||||
log: logApi,
|
||||
user: userApi,
|
||||
thData: thDataApi,
|
||||
alert: alertApi,
|
||||
event: eventApi
|
||||
}
|
||||
// 温湿度区间设置接口
|
||||
export const wsdApi = {
|
||||
// 更新温湿度区间设置
|
||||
update(data) {
|
||||
return httpService.post('/api/wsd', data)
|
||||
},
|
||||
|
||||
// 获取温湿度区间设置
|
||||
getById(id) {
|
||||
return httpService.get(`/api/wsd/${id}`)
|
||||
},
|
||||
|
||||
getLatest() {
|
||||
return httpService.get('/api/data/latest')
|
||||
}
|
||||
}
|
||||
|
||||
export default {}
|
||||
@ -1,213 +0,0 @@
|
||||
/**
|
||||
* 历史数据接口使用示例
|
||||
* 演示如何使用封装的历史数据API
|
||||
*/
|
||||
|
||||
import { dataHistoryApi } from './api.js'
|
||||
|
||||
// 使用示例类
|
||||
class DataHistoryExample {
|
||||
|
||||
// 示例1: 获取指定时间范围的历史数据
|
||||
async getHistoryData() {
|
||||
try {
|
||||
const params = {
|
||||
startTime: "2025-09-30 06:51:40",
|
||||
endTime: "2025-09-30 23:51:40"
|
||||
}
|
||||
|
||||
console.log('📊 请求历史数据:', params)
|
||||
|
||||
const response = await dataHistoryApi.getHistory(params)
|
||||
|
||||
console.log('✅ 历史数据获取成功:', response)
|
||||
return response
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 历史数据获取失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 示例2: 获取今天的历史数据
|
||||
async getTodayHistory() {
|
||||
try {
|
||||
const today = new Date()
|
||||
const startTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0)
|
||||
const endTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59)
|
||||
|
||||
const params = {
|
||||
startTime: this.formatDateTime(startTime),
|
||||
endTime: this.formatDateTime(endTime)
|
||||
}
|
||||
|
||||
console.log('📊 请求今天历史数据:', params)
|
||||
|
||||
const response = await dataHistoryApi.getHistory(params)
|
||||
|
||||
console.log('✅ 今天历史数据获取成功:', response)
|
||||
return response
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 今天历史数据获取失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 示例3: 获取最近7天的历史数据
|
||||
async getLastWeekHistory() {
|
||||
try {
|
||||
const endTime = new Date()
|
||||
const startTime = new Date(endTime.getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||
|
||||
const params = {
|
||||
startTime: this.formatDateTime(startTime),
|
||||
endTime: this.formatDateTime(endTime)
|
||||
}
|
||||
|
||||
console.log('📊 请求最近7天历史数据:', params)
|
||||
|
||||
const response = await dataHistoryApi.getHistory(params)
|
||||
|
||||
console.log('✅ 最近7天历史数据获取成功:', response)
|
||||
return response
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 最近7天历史数据获取失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 示例4: 获取指定小时的历史数据
|
||||
async getHourlyHistory(date, hour) {
|
||||
try {
|
||||
const startTime = new Date(date)
|
||||
startTime.setHours(hour, 0, 0, 0)
|
||||
|
||||
const endTime = new Date(startTime)
|
||||
endTime.setHours(hour + 1, 0, 0, 0)
|
||||
|
||||
const params = {
|
||||
startTime: this.formatDateTime(startTime),
|
||||
endTime: this.formatDateTime(endTime)
|
||||
}
|
||||
|
||||
console.log('📊 请求指定小时历史数据:', params)
|
||||
|
||||
const response = await dataHistoryApi.getHistory(params)
|
||||
|
||||
console.log('✅ 指定小时历史数据获取成功:', response)
|
||||
return response
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 指定小时历史数据获取失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化日期时间
|
||||
formatDateTime(date) {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
// 示例5: 在Vue组件中使用
|
||||
async useInVueComponent() {
|
||||
// 在Vue组件的methods中使用
|
||||
const methods = {
|
||||
async loadHistoryData() {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '加载历史数据中...'
|
||||
})
|
||||
|
||||
const params = {
|
||||
startTime: "2025-09-30 06:51:40",
|
||||
endTime: "2025-09-30 23:51:40"
|
||||
}
|
||||
|
||||
// 使用全局注册的API
|
||||
const response = await this.$api.dataHistory.getHistory(params)
|
||||
|
||||
console.log('历史数据:', response)
|
||||
|
||||
// 处理响应数据
|
||||
if (response.code === 200) {
|
||||
this.historyData = response.data
|
||||
uni.showToast({
|
||||
title: '数据加载成功',
|
||||
icon: 'success'
|
||||
})
|
||||
} else {
|
||||
throw new Error(response.message || '数据加载失败')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载历史数据失败:', error)
|
||||
uni.showToast({
|
||||
title: error.message || '数据加载失败',
|
||||
icon: 'error'
|
||||
})
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return methods
|
||||
}
|
||||
}
|
||||
|
||||
// 创建实例
|
||||
const dataHistoryExample = new DataHistoryExample()
|
||||
|
||||
export default dataHistoryExample
|
||||
|
||||
// 使用示例
|
||||
/*
|
||||
// 1. 直接使用API
|
||||
import { dataHistoryApi } from '@/utils/api.js'
|
||||
|
||||
const getData = async () => {
|
||||
try {
|
||||
const response = await dataHistoryApi.getHistory({
|
||||
startTime: "2025-09-30 06:51:40",
|
||||
endTime: "2025-09-30 23:51:40"
|
||||
})
|
||||
console.log('历史数据:', response)
|
||||
} catch (error) {
|
||||
console.error('获取失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 在Vue组件中使用
|
||||
export default {
|
||||
methods: {
|
||||
async loadData() {
|
||||
try {
|
||||
const response = await this.$api.dataHistory.getHistory({
|
||||
startTime: "2025-09-30 06:51:40",
|
||||
endTime: "2025-09-30 23:51:40"
|
||||
})
|
||||
this.data = response.data
|
||||
} catch (error) {
|
||||
this.$toast(error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 使用示例类
|
||||
import dataHistoryExample from '@/utils/dataHistoryExample.js'
|
||||
|
||||
const example = new dataHistoryExample()
|
||||
example.getHistoryData()
|
||||
example.getTodayHistory()
|
||||
example.getLastWeekHistory()
|
||||
*/
|
||||
209
src/utils/ezvizDeviceChecker.js
Normal file
209
src/utils/ezvizDeviceChecker.js
Normal file
@ -0,0 +1,209 @@
|
||||
// 萤石云设备检查工具
|
||||
import tokenManager from './ezvizTokenManager.js'
|
||||
|
||||
class EzvizDeviceChecker {
|
||||
constructor() {
|
||||
this.baseUrl = 'https://open.ys7.com/api/lapp'
|
||||
}
|
||||
|
||||
// 检查设备是否存在
|
||||
async checkDevice(deviceSerial) {
|
||||
try {
|
||||
console.log('🔍 检查设备:', deviceSerial)
|
||||
|
||||
const accessToken = await tokenManager.getValidAccessToken()
|
||||
|
||||
const response = await uni.request({
|
||||
url: `${this.baseUrl}/device/info`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data: {
|
||||
accessToken: accessToken,
|
||||
deviceSerial: deviceSerial
|
||||
}
|
||||
})
|
||||
|
||||
console.log('📡 设备信息响应:', response)
|
||||
|
||||
if (response.data && response.data.code === '200') {
|
||||
const deviceInfo = response.data.data
|
||||
console.log('✅ 设备信息:', deviceInfo)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
device: deviceInfo,
|
||||
isOnline: deviceInfo.status === 1,
|
||||
deviceName: deviceInfo.deviceName,
|
||||
deviceType: deviceInfo.deviceType
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 设备查询失败:', response.data?.msg)
|
||||
return {
|
||||
success: false,
|
||||
error: response.data?.msg || '设备查询失败'
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 设备检查异常:', error)
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取设备列表
|
||||
async getDeviceList() {
|
||||
try {
|
||||
console.log('📋 获取设备列表')
|
||||
|
||||
const accessToken = await tokenManager.getValidAccessToken()
|
||||
|
||||
const response = await uni.request({
|
||||
url: `${this.baseUrl}/device/list`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data: {
|
||||
accessToken: accessToken,
|
||||
pageStart: 0,
|
||||
pageSize: 50
|
||||
}
|
||||
})
|
||||
|
||||
console.log('📡 设备列表响应:', response)
|
||||
|
||||
if (response.data && response.data.code === '200') {
|
||||
const devices = response.data.data
|
||||
console.log('✅ 设备列表:', devices)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
devices: devices,
|
||||
total: devices.length
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 设备列表获取失败:', response.data?.msg)
|
||||
return {
|
||||
success: false,
|
||||
error: response.data?.msg || '设备列表获取失败'
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 设备列表获取异常:', error)
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查直播地址是否有效
|
||||
async checkLiveUrl(deviceSerial, channelNo = 1) {
|
||||
try {
|
||||
console.log('🎥 检查直播地址:', deviceSerial, channelNo)
|
||||
|
||||
const accessToken = await tokenManager.getValidAccessToken()
|
||||
|
||||
const response = await uni.request({
|
||||
url: `${this.baseUrl}/device/live`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data: {
|
||||
accessToken: accessToken,
|
||||
deviceSerial: deviceSerial,
|
||||
channelNo: channelNo,
|
||||
protocol: 1 // 1-rtmp,2-hls,3-flv
|
||||
}
|
||||
})
|
||||
|
||||
console.log('📡 直播地址响应:', response)
|
||||
|
||||
if (response.data && response.data.code === '200') {
|
||||
const liveInfo = response.data.data
|
||||
console.log('✅ 直播地址信息:', liveInfo)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
liveUrl: liveInfo.url,
|
||||
hd: liveInfo.hd,
|
||||
sd: liveInfo.sd
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 直播地址获取失败:', response.data?.msg)
|
||||
return {
|
||||
success: false,
|
||||
error: response.data?.msg || '直播地址获取失败'
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 直播地址检查异常:', error)
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 从URL中提取设备序列号
|
||||
extractDeviceSerial(playUrl) {
|
||||
try {
|
||||
// ezopen://open.ys7.com/K74237657/1.hd.live
|
||||
const match = playUrl.match(/ezopen:\/\/open\.ys7\.com\/([^\/]+)\//)
|
||||
return match ? match[1] : null
|
||||
} catch (error) {
|
||||
console.error('提取设备序列号失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 综合检查
|
||||
async comprehensiveCheck(playUrl) {
|
||||
console.log('🔍 开始综合检查播放地址:', playUrl)
|
||||
|
||||
const deviceSerial = this.extractDeviceSerial(playUrl)
|
||||
if (!deviceSerial) {
|
||||
return {
|
||||
success: false,
|
||||
error: '无法从播放地址中提取设备序列号'
|
||||
}
|
||||
}
|
||||
|
||||
console.log('📱 提取到设备序列号:', deviceSerial)
|
||||
|
||||
// 检查设备信息
|
||||
const deviceCheck = await this.checkDevice(deviceSerial)
|
||||
if (!deviceCheck.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: `设备检查失败: ${deviceCheck.error}`,
|
||||
deviceSerial: deviceSerial
|
||||
}
|
||||
}
|
||||
|
||||
// 检查直播地址
|
||||
const liveCheck = await this.checkLiveUrl(deviceSerial)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
deviceSerial: deviceSerial,
|
||||
device: deviceCheck.device,
|
||||
isOnline: deviceCheck.isOnline,
|
||||
liveUrl: liveCheck.success ? liveCheck.liveUrl : null,
|
||||
liveError: liveCheck.success ? null : liveCheck.error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
const deviceChecker = new EzvizDeviceChecker()
|
||||
export default deviceChecker
|
||||
|
||||
164
src/utils/ezvizTokenManager.js
Normal file
164
src/utils/ezvizTokenManager.js
Normal file
@ -0,0 +1,164 @@
|
||||
// 萤石云AccessToken管理工具
|
||||
// 使用方法:
|
||||
// 1. 在萤石云开放平台获取AppKey和AppSecret
|
||||
// 2. 调用getAccessToken()获取新的token
|
||||
|
||||
class EzvizTokenManager {
|
||||
constructor(appKey, appSecret) {
|
||||
this.appKey = appKey
|
||||
this.appSecret = appSecret
|
||||
this.baseUrl = 'https://open.ys7.com/api/lapp'
|
||||
}
|
||||
|
||||
// 获取AccessToken
|
||||
async getAccessToken() {
|
||||
try {
|
||||
console.log('🔑 开始获取AccessToken...')
|
||||
|
||||
const response = await uni.request({
|
||||
url: `${this.baseUrl}/token/get`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data: {
|
||||
appKey: this.appKey,
|
||||
appSecret: this.appSecret
|
||||
}
|
||||
})
|
||||
|
||||
console.log('📡 API响应:', response)
|
||||
|
||||
if (response.data && response.data.code === '200') {
|
||||
const tokenData = response.data.data
|
||||
console.log('✅ AccessToken获取成功:', tokenData)
|
||||
|
||||
// 保存到本地存储
|
||||
uni.setStorageSync('ezviz_access_token', tokenData.accessToken)
|
||||
uni.setStorageSync('ezviz_token_expire', tokenData.expireTime)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
accessToken: tokenData.accessToken,
|
||||
expireTime: tokenData.expireTime
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.data?.msg || '获取AccessToken失败')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 获取AccessToken失败:', error)
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查token是否过期
|
||||
isTokenExpired() {
|
||||
const expireTime = uni.getStorageSync('ezviz_token_expire')
|
||||
if (!expireTime) return true
|
||||
|
||||
const now = Date.now()
|
||||
return now >= expireTime * 1000 // expireTime是秒,需要转换为毫秒
|
||||
}
|
||||
|
||||
// 获取有效的AccessToken
|
||||
async getValidAccessToken() {
|
||||
// let token = uni.getStorageSync('ezviz_access_token')
|
||||
|
||||
// if (!token || this.isTokenExpired()) {
|
||||
// console.log('🔄 Token不存在或已过期,重新获取...')
|
||||
// const result = await this.getAccessToken()
|
||||
|
||||
// if (result.success) {
|
||||
// return result.accessToken
|
||||
// } else {
|
||||
// throw new Error(result.error)
|
||||
// }
|
||||
// }
|
||||
const result = await this.getAccessToken()
|
||||
|
||||
if (result.success) {
|
||||
return result.accessToken
|
||||
} else {
|
||||
throw new Error(result.error)
|
||||
}
|
||||
|
||||
console.log('✅ 使用缓存的AccessToken')
|
||||
return token
|
||||
}
|
||||
|
||||
// 获取设备信息
|
||||
async getDeviceList() {
|
||||
try {
|
||||
const accessToken = await this.getValidAccessToken()
|
||||
const response = await uni.request({
|
||||
url: `${this.baseUrl}/device/list`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data: {
|
||||
accessToken: accessToken,
|
||||
pageStart: 0,
|
||||
pageSize: 50
|
||||
}
|
||||
})
|
||||
|
||||
if (response.data && response.data.code === '200') {
|
||||
return {
|
||||
success: true,
|
||||
devices: response.data.data
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.data?.msg || '获取设备列表失败')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 获取设备列表失败:', error)
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
const tokenManager = new EzvizTokenManager(
|
||||
// '19c3a50bc19a4b27832408e003797644', // 你的AppKey
|
||||
// 'cf43a4f58bc64d7e37bba9947daf70b3' // 你的AppSecret
|
||||
'19c3a50bc19a4b27832408e003797644',
|
||||
'cf43a4f58bc64d7e37bba9947daf70b3',
|
||||
)
|
||||
|
||||
export default tokenManager
|
||||
|
||||
// 使用示例:
|
||||
/*
|
||||
import tokenManager from '@/utils/ezvizTokenManager.js'
|
||||
|
||||
// 获取新的AccessToken
|
||||
const result = await tokenManager.getAccessToken()
|
||||
if (result.success) {
|
||||
console.log('新的AccessToken:', result.accessToken)
|
||||
} else {
|
||||
console.error('获取失败:', result.error)
|
||||
}
|
||||
|
||||
// 获取有效的AccessToken(自动处理过期)
|
||||
try {
|
||||
const token = await tokenManager.getValidAccessToken()
|
||||
console.log('有效的AccessToken:', token)
|
||||
} catch (error) {
|
||||
console.error('获取AccessToken失败:', error)
|
||||
}
|
||||
|
||||
// 获取设备列表
|
||||
const devices = await tokenManager.getDeviceList()
|
||||
if (devices.success) {
|
||||
console.log('设备列表:', devices.devices)
|
||||
}
|
||||
*/
|
||||
@ -106,11 +106,11 @@ const initEventHandleMqtt = (topicUrl) => {
|
||||
console.log("✅ MQTT连接成功");
|
||||
|
||||
// 显示连接成功提示
|
||||
uni.showToast({
|
||||
title: 'MQTT连接成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
// uni.showToast({
|
||||
// title: 'MQTT连接成功',
|
||||
// icon: 'success',
|
||||
// duration: 2000
|
||||
// });
|
||||
|
||||
//订阅主题
|
||||
client.subscribe(topicUrl, function(err) {
|
||||
|
||||
Reference in New Issue
Block a user