moki 3 dienas atpakaļ
vecāks
revīzija
73302f0342
3 mainītis faili ar 85 papildinājumiem un 8 dzēšanām
  1. 6 0
      src/api/status.ts
  2. 8 3
      src/types/index.ts
  3. 71 5
      src/views/Dashboard.vue

+ 6 - 0
src/api/status.ts

@@ -0,0 +1,6 @@
+import http from './index'
+import type {ApiResponse, DeviceStatus} from '@/types'
+
+export function getStatus() {
+    return http.get<ApiResponse<DeviceStatus>>('/status')
+}

+ 8 - 3
src/types/index.ts

@@ -72,9 +72,14 @@ export interface PortState {
 }
 
 export interface StatusRecord {
-    id: number
-    code: string
-    timestamp: string
+  id: number
+  code: string
+  timestamp: string
+}
+
+export interface DeviceStatus {
+  mqtt: { connected: boolean; broker: string }
+  ble: { running: boolean; device: string }
 }
 
 export const STATUS_CONFIG: Record<StatusCode, { color: string; label: string }> = {

+ 71 - 5
src/views/Dashboard.vue

@@ -3,11 +3,12 @@ import {computed, nextTick, onMounted, onUnmounted, ref, watch} from 'vue'
 import * as echarts from 'echarts'
 import {useSSE} from '@/composables/useSSE'
 import {useTheme} from '@/composables/useTheme'
-import type {SseMessage, StatusCode, StatusRecord} from '@/types'
+import type {DeviceStatus, SseMessage, StatusCode, StatusRecord} from '@/types'
 import {STATUS_CONFIG} from '@/types'
 import StatusCard from '@/components/StatusCard.vue'
 import http from '@/api/index'
 import {getHistory} from '@/api/history'
+import {getStatus} from '@/api/status'
 
 const {connected, onMessage, onDisconnect} = useSSE('/api/events')
 const {theme} = useTheme()
@@ -20,6 +21,7 @@ const statusSince = ref(Date.now())
 const statusDuration = ref('0s')
 const connectSince = ref<number | null>(null)
 const connectDuration = ref('')
+const deviceStatus = ref<DeviceStatus | null>(null)
 
 let clockTimer: ReturnType<typeof setInterval> | null = null
 let durationTimer: ReturnType<typeof setInterval> | null = null
@@ -259,6 +261,7 @@ onMounted(() => {
   durationTimer = setInterval(updateDurations, 1000)
 
   fetchHistory()
+  fetchStatus()
 
   if (pieChartRef.value) {
     pieChart = echarts.init(pieChartRef.value)
@@ -303,6 +306,17 @@ async function fetchHistory() {
   }
 }
 
+async function fetchStatus() {
+  try {
+    const res = await getStatus()
+    const data = res.data
+    if (data.code === 0 && data.data) {
+      deviceStatus.value = data.data
+    }
+  } catch {
+  }
+}
+
 const statusLabel = computed(() => STATUS_CONFIG[currentCode.value]?.label ?? currentCode.value)
 
 const greeting = computed(() => {
@@ -377,6 +391,18 @@ async function sendCode(code: string) {
         <div class="section-title">今日工作时长</div>
         <div class="work-duration">{{ workDuration }}</div>
       </div>
+      <div class="device-status-section">
+        <div class="section-title">设备状态</div>
+        <div v-if="deviceStatus?.mqtt.connected" class="device-connected">
+          <span class="device-dot connected"/> MQTT ● 已连接
+        </div>
+        <div v-else-if="deviceStatus?.ble.running" class="device-connected">
+          <span class="device-dot connected"/> BLE ● 已连接
+        </div>
+        <div v-else class="device-disconnected">
+          <span class="device-dot disconnected"/> 设备未连接
+        </div>
+      </div>
       <div class="stats-section">
         <div class="section-title">今日统计</div>
         <div v-if="todayStats.length === 0" class="stats-empty">暂无数据</div>
@@ -614,9 +640,6 @@ async function sendCode(code: string) {
   border: 1px solid var(--border-color);
   border-radius: 8px;
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
 }
 
 .work-duration {
@@ -625,7 +648,50 @@ async function sendCode(code: string) {
   color: #1890ff;
   font-variant-numeric: tabular-nums;
   line-height: 1;
-  margin-top: 4px;
+}
+
+.device-status-section {
+  width: 180px;
+  flex-shrink: 0;
+  padding: 16px 20px;
+  background: var(--card-bg);
+  border: 1px solid var(--border-color);
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+}
+
+.device-connected {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 14px;
+  font-weight: 600;
+  color: #52c41a;
+}
+
+.device-disconnected {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 14px;
+  font-weight: 600;
+  color: var(--text-secondary);
+}
+
+.device-dot {
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  flex-shrink: 0;
+}
+
+.device-dot.connected {
+  background: #52c41a;
+  box-shadow: 0 0 6px rgba(82, 196, 26, 0.5);
+}
+
+.device-dot.disconnected {
+  background: var(--text-secondary);
 }
 
 .stats-section {