|
@@ -230,18 +230,32 @@
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="device-location">{{ device.location }}</div>
|
|
<div class="device-location">{{ device.location }}</div>
|
|
|
- <div class="device-metrics">
|
|
|
|
|
- <div class="metric">
|
|
|
|
|
- <div class="metric-label">{{ device.metrics[0].name }}</div>
|
|
|
|
|
- <div class="metric-value" :class="{ 'metric-warning': device.status === 'warning' && device.metrics[0].isAlert }">
|
|
|
|
|
- {{ device.metrics[0].value }}{{ device.metrics[0].unit }}
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="metric">
|
|
|
|
|
- <div class="metric-label">{{ device.metrics[1].name }}</div>
|
|
|
|
|
- <div class="metric-value" :class="{ 'metric-warning': device.status === 'warning' && device.metrics[1].isAlert }">
|
|
|
|
|
- {{ device.metrics[1].value }}{{ device.metrics[1].unit }}
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div class="device-metrics"
|
|
|
|
|
+ @mouseenter="pauseCarousel(device.id)"
|
|
|
|
|
+ @mouseleave="resumeCarousel(device.id)">
|
|
|
|
|
+ <div class="metrics-container">
|
|
|
|
|
+ <transition name="flip" mode="out-in">
|
|
|
|
|
+ <div class="metric-pair" :key="getCurrentMetricIndex(device.id)">
|
|
|
|
|
+ <div class="metric">
|
|
|
|
|
+ <div class="metric-label">{{ getCurrentMetrics(device)[0].name }}</div>
|
|
|
|
|
+ <div class="metric-value" :class="{
|
|
|
|
|
+ 'metric-warning': device.status === 'warning' && getCurrentMetrics(device)[0].isAlert,
|
|
|
|
|
+ 'metric-alert': getCurrentMetrics(device)[0].isAlert
|
|
|
|
|
+ }">
|
|
|
|
|
+ {{ getCurrentMetrics(device)[0].value }}{{ getCurrentMetrics(device)[0].unit }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="metric" v-if="getCurrentMetrics(device)[1]">
|
|
|
|
|
+ <div class="metric-label">{{ getCurrentMetrics(device)[1].name }}</div>
|
|
|
|
|
+ <div class="metric-value" :class="{
|
|
|
|
|
+ 'metric-warning': device.status === 'warning' && getCurrentMetrics(device)[1].isAlert,
|
|
|
|
|
+ 'metric-alert': getCurrentMetrics(device)[1].isAlert
|
|
|
|
|
+ }">
|
|
|
|
|
+ {{ getCurrentMetrics(device)[1].value }}{{ getCurrentMetrics(device)[1].unit }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </transition>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="device-footer">
|
|
<div class="device-footer">
|
|
@@ -574,6 +588,10 @@ export default {
|
|
|
{ id: 12, name: '北区3号摄像头', location: '北区3号地块', status: 'online' }
|
|
{ id: 12, name: '北区3号摄像头', location: '北区3号地块', status: 'online' }
|
|
|
],
|
|
],
|
|
|
|
|
|
|
|
|
|
+ // 轮播控制状态
|
|
|
|
|
+ carouselStates: {},
|
|
|
|
|
+ carouselTimers: {},
|
|
|
|
|
+
|
|
|
deviceList: [
|
|
deviceList: [
|
|
|
{
|
|
{
|
|
|
id: 1,
|
|
id: 1,
|
|
@@ -585,7 +603,9 @@ export default {
|
|
|
type: 'soil',
|
|
type: 'soil',
|
|
|
metrics: [
|
|
metrics: [
|
|
|
{ name: '土壤湿度', value: '32.5', unit: '%', isAlert: false },
|
|
{ name: '土壤湿度', value: '32.5', unit: '%', isAlert: false },
|
|
|
- { name: '土壤温度', value: '24.2', unit: '℃', isAlert: false }
|
|
|
|
|
|
|
+ { name: '土壤温度', value: '24.2', unit: '℃', isAlert: false },
|
|
|
|
|
+ { name: 'EC值', value: '1.8', unit: 'mS/cm', isAlert: false },
|
|
|
|
|
+ { name: 'N含量', value: '45', unit: 'mg/kg', isAlert: false }
|
|
|
]
|
|
]
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
@@ -598,7 +618,10 @@ export default {
|
|
|
type: 'water',
|
|
type: 'water',
|
|
|
metrics: [
|
|
metrics: [
|
|
|
{ name: 'pH值', value: '8.5', unit: '', isAlert: true },
|
|
{ name: 'pH值', value: '8.5', unit: '', isAlert: true },
|
|
|
- { name: '溶解氧', value: '6.2', unit: 'mg/L', isAlert: false }
|
|
|
|
|
|
|
+ { name: '溶解氧', value: '6.2', unit: 'mg/L', isAlert: false },
|
|
|
|
|
+ { name: '电导率', value: '1.2', unit: 'mS/cm', isAlert: false },
|
|
|
|
|
+ { name: '浊度', value: '15', unit: 'NTU', isAlert: false },
|
|
|
|
|
+ { name: '氨氮', value: '0.8', unit: 'mg/L', isAlert: false }
|
|
|
]
|
|
]
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
@@ -611,7 +634,11 @@ export default {
|
|
|
type: 'weather',
|
|
type: 'weather',
|
|
|
metrics: [
|
|
metrics: [
|
|
|
{ name: '温度', value: '26.5', unit: '℃', isAlert: false },
|
|
{ name: '温度', value: '26.5', unit: '℃', isAlert: false },
|
|
|
- { name: '湿度', value: '68', unit: '%', isAlert: false }
|
|
|
|
|
|
|
+ { name: '湿度', value: '68', unit: '%', isAlert: false },
|
|
|
|
|
+ { name: '光照强度', value: '45000', unit: 'lux', isAlert: false },
|
|
|
|
|
+ { name: '风速', value: '2.3', unit: 'm/s', isAlert: false },
|
|
|
|
|
+ { name: '降雨量', value: '0', unit: 'mm', isAlert: false },
|
|
|
|
|
+ { name: '气压', value: '1013', unit: 'hPa', isAlert: false }
|
|
|
]
|
|
]
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
@@ -637,7 +664,9 @@ export default {
|
|
|
type: 'water',
|
|
type: 'water',
|
|
|
metrics: [
|
|
metrics: [
|
|
|
{ name: 'pH值', value: '7.2', unit: '', isAlert: false },
|
|
{ name: 'pH值', value: '7.2', unit: '', isAlert: false },
|
|
|
- { name: '溶解氧', value: '5.8', unit: 'mg/L', isAlert: false }
|
|
|
|
|
|
|
+ { name: '溶解氧', value: '5.8', unit: 'mg/L', isAlert: false },
|
|
|
|
|
+ { name: '电导率', value: '0.9', unit: 'mS/cm', isAlert: false },
|
|
|
|
|
+ { name: '浊度', value: '8', unit: 'NTU', isAlert: false }
|
|
|
]
|
|
]
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
@@ -650,7 +679,10 @@ export default {
|
|
|
type: 'weather',
|
|
type: 'weather',
|
|
|
metrics: [
|
|
metrics: [
|
|
|
{ name: '温度', value: '35.8', unit: '℃', isAlert: true },
|
|
{ name: '温度', value: '35.8', unit: '℃', isAlert: true },
|
|
|
- { name: '湿度', value: '45', unit: '%', isAlert: false }
|
|
|
|
|
|
|
+ { name: '湿度', value: '45', unit: '%', isAlert: false },
|
|
|
|
|
+ { name: '光照强度', value: '52000', unit: 'lux', isAlert: false },
|
|
|
|
|
+ { name: '风速', value: '4.2', unit: 'm/s', isAlert: false },
|
|
|
|
|
+ { name: '降雨量', value: '2.5', unit: 'mm', isAlert: false }
|
|
|
]
|
|
]
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
@@ -663,7 +695,10 @@ export default {
|
|
|
type: 'soil',
|
|
type: 'soil',
|
|
|
metrics: [
|
|
metrics: [
|
|
|
{ name: '土壤湿度', value: '28.5', unit: '%', isAlert: false },
|
|
{ name: '土壤湿度', value: '28.5', unit: '%', isAlert: false },
|
|
|
- { name: '土壤温度', value: '22.8', unit: '℃', isAlert: false }
|
|
|
|
|
|
|
+ { name: '土壤温度', value: '22.8', unit: '℃', isAlert: false },
|
|
|
|
|
+ { name: 'EC值', value: '2.1', unit: 'mS/cm', isAlert: false },
|
|
|
|
|
+ { name: 'P含量', value: '38', unit: 'mg/kg', isAlert: false },
|
|
|
|
|
+ { name: 'K含量', value: '125', unit: 'mg/kg', isAlert: false }
|
|
|
]
|
|
]
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
@@ -676,7 +711,10 @@ export default {
|
|
|
type: 'water',
|
|
type: 'water',
|
|
|
metrics: [
|
|
metrics: [
|
|
|
{ name: 'pH值', value: '7.1', unit: '', isAlert: false },
|
|
{ name: 'pH值', value: '7.1', unit: '', isAlert: false },
|
|
|
- { name: '溶解氧', value: '6.5', unit: 'mg/L', isAlert: false }
|
|
|
|
|
|
|
+ { name: '溶解氧', value: '6.5', unit: 'mg/L', isAlert: false },
|
|
|
|
|
+ { name: '电导率', value: '1.1', unit: 'mS/cm', isAlert: false },
|
|
|
|
|
+ { name: '温度', value: '23.5', unit: '℃', isAlert: false },
|
|
|
|
|
+ { name: '浊度', value: '5', unit: 'NTU', isAlert: false }
|
|
|
]
|
|
]
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
@@ -702,7 +740,11 @@ export default {
|
|
|
type: 'weather',
|
|
type: 'weather',
|
|
|
metrics: [
|
|
metrics: [
|
|
|
{ name: '温度', value: '28.2', unit: '℃', isAlert: false },
|
|
{ name: '温度', value: '28.2', unit: '℃', isAlert: false },
|
|
|
- { name: '湿度', value: '72', unit: '%', isAlert: false }
|
|
|
|
|
|
|
+ { name: '湿度', value: '72', unit: '%', isAlert: false },
|
|
|
|
|
+ { name: '光照强度', value: '38000', unit: 'lux', isAlert: false },
|
|
|
|
|
+ { name: '风速', value: '1.8', unit: 'm/s', isAlert: false },
|
|
|
|
|
+ { name: '气压', value: '1015', unit: 'hPa', isAlert: false },
|
|
|
|
|
+ { name: 'UV指数', value: '8', unit: '', isAlert: false }
|
|
|
]
|
|
]
|
|
|
}
|
|
}
|
|
|
]
|
|
]
|
|
@@ -1155,6 +1197,65 @@ export default {
|
|
|
rainfall: '降雨量'
|
|
rainfall: '降雨量'
|
|
|
}
|
|
}
|
|
|
return indicatorMap[value] || value
|
|
return indicatorMap[value] || value
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 轮播控制方法
|
|
|
|
|
+ initializeCarousels() {
|
|
|
|
|
+ this.deviceList.forEach(device => {
|
|
|
|
|
+ if (device.metrics.length > 2) {
|
|
|
|
|
+ this.$set(this.carouselStates, device.id, 0)
|
|
|
|
|
+ this.startCarouselTimer(device.id)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ startCarouselTimer(deviceId) {
|
|
|
|
|
+ if (this.carouselTimers[deviceId]) {
|
|
|
|
|
+ clearInterval(this.carouselTimers[deviceId])
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.carouselTimers[deviceId] = setInterval(() => {
|
|
|
|
|
+ this.nextMetric(deviceId)
|
|
|
|
|
+ }, 3000) // 3秒切换一次
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ pauseCarousel(deviceId) {
|
|
|
|
|
+ if (this.carouselTimers[deviceId]) {
|
|
|
|
|
+ clearInterval(this.carouselTimers[deviceId])
|
|
|
|
|
+ this.carouselTimers[deviceId] = null
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ resumeCarousel(deviceId) {
|
|
|
|
|
+ const device = this.deviceList.find(d => d.id === deviceId)
|
|
|
|
|
+ if (device && device.metrics.length > 2) {
|
|
|
|
|
+ this.startCarouselTimer(deviceId)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ nextMetric(deviceId) {
|
|
|
|
|
+ const device = this.deviceList.find(d => d.id === deviceId)
|
|
|
|
|
+ if (!device || device.metrics.length <= 2) return
|
|
|
|
|
+
|
|
|
|
|
+ const totalGroups = Math.ceil(device.metrics.length / 2)
|
|
|
|
|
+ const currentIndex = this.carouselStates[deviceId] || 0
|
|
|
|
|
+ const nextIndex = (currentIndex + 1) % totalGroups
|
|
|
|
|
+
|
|
|
|
|
+ this.$set(this.carouselStates, deviceId, nextIndex)
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ getCurrentMetricIndex(deviceId) {
|
|
|
|
|
+ return this.carouselStates[deviceId] || 0
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ getCurrentMetrics(device) {
|
|
|
|
|
+ if (device.metrics.length <= 2) {
|
|
|
|
|
+ return device.metrics
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const currentIndex = this.carouselStates[device.id] || 0
|
|
|
|
|
+ const startIndex = currentIndex * 2
|
|
|
|
|
+ return device.metrics.slice(startIndex, startIndex + 2)
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
|
|
@@ -1172,6 +1273,9 @@ export default {
|
|
|
appEl.style.backgroundColor = '#f8fafc'
|
|
appEl.style.backgroundColor = '#f8fafc'
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 初始化轮播
|
|
|
|
|
+ this.initializeCarousels()
|
|
|
|
|
+
|
|
|
// 监听键盘事件
|
|
// 监听键盘事件
|
|
|
document.addEventListener('keydown', (e) => {
|
|
document.addEventListener('keydown', (e) => {
|
|
|
if (e.key === 'Escape') {
|
|
if (e.key === 'Escape') {
|
|
@@ -1202,6 +1306,14 @@ export default {
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
beforeDestroy() {
|
|
beforeDestroy() {
|
|
|
|
|
+ // 清理所有轮播定时器
|
|
|
|
|
+ Object.values(this.carouselTimers).forEach(timer => {
|
|
|
|
|
+ if (timer) {
|
|
|
|
|
+ clearInterval(timer)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ this.carouselTimers = {}
|
|
|
|
|
+
|
|
|
// 恢复原始背景色
|
|
// 恢复原始背景色
|
|
|
document.body.style.backgroundColor = this.originalBodyBg
|
|
document.body.style.backgroundColor = this.originalBodyBg
|
|
|
document.body.style.color = this.originalBodyColor
|
|
document.body.style.color = this.originalBodyColor
|
|
@@ -1991,11 +2103,21 @@ export default {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.device-metrics {
|
|
.device-metrics {
|
|
|
|
|
+ margin-bottom: 1.5rem;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.metrics-container {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ min-height: 80px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.metric-pair {
|
|
|
display: grid;
|
|
display: grid;
|
|
|
grid-template-columns: 1fr 1fr;
|
|
grid-template-columns: 1fr 1fr;
|
|
|
gap: 2rem;
|
|
gap: 2rem;
|
|
|
- margin-bottom: 1.5rem;
|
|
|
|
|
- flex: 1;
|
|
|
|
|
|
|
+ width: 100%;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.metric {
|
|
.metric {
|
|
@@ -2028,6 +2150,37 @@ export default {
|
|
|
color: #ef4444;
|
|
color: #ef4444;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.metric-value.metric-alert {
|
|
|
|
|
+ color: #ef4444;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 机场翻牌动画效果 */
|
|
|
|
|
+.flip-enter-active,
|
|
|
|
|
+.flip-leave-active {
|
|
|
|
|
+ transition: all 0.4s ease-in-out;
|
|
|
|
|
+ transform-style: preserve-3d;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.flip-enter {
|
|
|
|
|
+ transform: perspective(600px) rotateX(90deg);
|
|
|
|
|
+ opacity: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.flip-enter-to {
|
|
|
|
|
+ transform: perspective(600px) rotateX(0deg);
|
|
|
|
|
+ opacity: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.flip-leave {
|
|
|
|
|
+ transform: perspective(600px) rotateX(0deg);
|
|
|
|
|
+ opacity: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.flip-leave-to {
|
|
|
|
|
+ transform: perspective(600px) rotateX(-90deg);
|
|
|
|
|
+ opacity: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.device-card.offline .metric-value {
|
|
.device-card.offline .metric-value {
|
|
|
color: #9ca3af;
|
|
color: #9ca3af;
|
|
|
}
|
|
}
|