Initial commit of akmon project
This commit is contained in:
315
pages/test/ap_monitor.uvue
Normal file
315
pages/test/ap_monitor.uvue
Normal file
@@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="nav-tabs">
|
||||
<button class="nav-tab active">智能网关</button>
|
||||
<button class="nav-tab" @click="switchPage">设备监测</button>
|
||||
</view>
|
||||
<view class="header">
|
||||
<text class="title">智能网关状态监测</text>
|
||||
<text class="subtitle">总数: {{ nodes.length }} | 在线: {{ onlineCount }}</text>
|
||||
</view>
|
||||
|
||||
<scroll-view class="grid-container" scroll-y="true">
|
||||
<view class="grid-layout">
|
||||
<view v-for="node in nodes" :key="node.id" class="card" :class="isOnline(node) ? 'card-online' : 'card-offline'">
|
||||
<view class="card-header">
|
||||
<text class="card-title">{{ node.name }}</text>
|
||||
<view class="status-badge" :class="isOnline(node) ? 'badge-online' : 'badge-offline'">
|
||||
<text class="badge-text">{{ isOnline(node) ? '在线' : '离线' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card-body">
|
||||
<view class="info-row">
|
||||
<text class="label">网关ID:</text>
|
||||
<text class="value">{{ node.mqtt_client_id }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">区域:</text>
|
||||
<text class="value">{{ node.region ?? '演示区域' }}</text>
|
||||
</view>
|
||||
<!-- <view class="info-row">
|
||||
<text class="label">版本:</text>
|
||||
<text class="value">{{ node.version ?? '-' }}</text>
|
||||
</view> -->
|
||||
<view class="info-row">
|
||||
<text class="label">最后更新:</text>
|
||||
<text class="value time-value">{{ formatTime(node.updated_at) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">更新时间:</text>
|
||||
<text class="value">{{ getTimeAgo(node.updated_at) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
import GatewayRealtimeService, { GatewayNode, GatewayRealtimeSubscription } from '@/utils/gatewayRealtimeService.uts'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
nodes: [] as GatewayNode[],
|
||||
subscription: null as GatewayRealtimeSubscription | null,
|
||||
now: Date.now(),
|
||||
timer: null as number | null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
onlineCount(): number {
|
||||
let count = 0
|
||||
for (let i = 0; i < this.nodes.length; i++) {
|
||||
if (this.isOnline(this.nodes[i])) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.startMonitoring()
|
||||
this.timer = setInterval(() => {
|
||||
this.now = Date.now()
|
||||
}, 1000)
|
||||
},
|
||||
onUnload() {
|
||||
this.stopMonitoring()
|
||||
if (this.timer != null) {
|
||||
clearInterval(this.timer!)
|
||||
this.timer = null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async startMonitoring() {
|
||||
this.subscription = await GatewayRealtimeService.subscribeGateways({
|
||||
onInitial: (initialNodes: GatewayNode[]) => {
|
||||
console.log('Initial nodes loaded:', initialNodes.length)
|
||||
this.nodes = initialNodes
|
||||
},
|
||||
onInsert: (node: GatewayNode) => {
|
||||
console.log('AP Node Inserted:', node.name)
|
||||
this.nodes.push(node)
|
||||
},
|
||||
onUpdate: (node: GatewayNode) => {
|
||||
console.log('AP Node Updated:', node.name, node.updated_at)
|
||||
const index = this.nodes.findIndex((n) => n.id == node.id)
|
||||
if (index >= 0) {
|
||||
// 使用 splice 确保视图能够响应数组元素的变化
|
||||
this.nodes.splice(index, 1, node)
|
||||
} else {
|
||||
// 如果列表中没有该节点(可能是初始加载遗漏或新节点),则添加
|
||||
console.log('Node not found in list, adding it:', node.name)
|
||||
this.nodes.push(node)
|
||||
}
|
||||
},
|
||||
onDelete: (id: string) => {
|
||||
const index = this.nodes.findIndex((n) => n.id == id)
|
||||
if (index >= 0) {
|
||||
this.nodes.splice(index, 1)
|
||||
}
|
||||
},
|
||||
onError: (err: any) => {
|
||||
console.error('Gateway monitoring error', err)
|
||||
uni.showToast({
|
||||
title: '连接失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
stopMonitoring() {
|
||||
if (this.subscription != null) {
|
||||
this.subscription!.dispose()
|
||||
this.subscription = null
|
||||
}
|
||||
},
|
||||
isOnline(node: GatewayNode): boolean {
|
||||
const updateTime = new Date(node.updated_at).getTime()
|
||||
// 60 seconds timeout
|
||||
return (this.now - updateTime) < 60000
|
||||
},
|
||||
formatTime(isoString: string): string {
|
||||
const date = new Date(isoString)
|
||||
return date.toLocaleTimeString()
|
||||
},
|
||||
getTimeAgo(isoString: string): string {
|
||||
const updateTime = new Date(isoString).getTime()
|
||||
const diff = Math.floor((this.now - updateTime) / 1000)
|
||||
if (diff < 0) return '刚刚'
|
||||
if (diff < 60) return `${diff}秒前`
|
||||
return `${Math.floor(diff / 60)}分前`
|
||||
},
|
||||
switchPage() {
|
||||
uni.redirectTo({
|
||||
url: '/pages/test/multi_device_monitor'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.page-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #fff;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.nav-tab {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
margin: 0 5px;
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.nav-tab.active {
|
||||
background-color: #007aff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 15px 20px;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.grid-layout {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 48%;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 15px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
|
||||
.card-online {
|
||||
border-left-color: #4caf50;
|
||||
}
|
||||
|
||||
.card-offline {
|
||||
border-left-color: #9e9e9e;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.badge-online {
|
||||
background-color: #e8f5e9;
|
||||
}
|
||||
|
||||
.badge-offline {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.badge-text {
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.badge-online .badge-text {
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.badge-offline .badge-text {
|
||||
color: #757575;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.time-value {
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user