Przeglądaj źródła

feat: 升级仪表盘为停车监控大屏——4统计卡片+设备状态+10秒轮询

lq 1 miesiąc temu
rodzic
commit
418324c3d5
2 zmienionych plików z 88 dodań i 155 usunięć
  1. 5 0
      frontend/src/api/dashboard.js
  2. 83 155
      frontend/src/view/dashboard/index.vue

+ 5 - 0
frontend/src/api/dashboard.js

@@ -0,0 +1,5 @@
+import service from '@/utils/request'
+
+export const getMonitor = () => {
+  return service({ url: '/dashboard/monitor', method: 'get' })
+}

+ 83 - 155
frontend/src/view/dashboard/index.vue

@@ -1,172 +1,100 @@
 <template>
-  <div class="page">
-    <div class="gva-card-box">
-      <div class="gva-card gva-top-card">
-        <div class="gva-top-card-left">
-          <div class="gva-top-card-left-title">早安,管理员,请开始一天的工作吧</div>
-          <div class="gva-top-card-left-dot">{{ weatherInfo }}</div>
-        </div>
-        <img
-          src="@/assets/dashboard.png"
-          class="gva-top-card-right"
-          alt
-        >
-      </div>
+  <div class="monitor-page">
+    <div class="monitor-header">
+      <h2>智慧停车监控</h2>
+      <span class="time">{{ now }}</span>
     </div>
-    
-    <div class="gva-card-box">
-      <div class="gva-card">
-        <div class="gva-card-title">数据统计</div>
-        <div class="p-4">
-          <el-row :gutter="20">
-            <el-col
-              :xs="24"
-              :sm="18"
-            >
-              <echarts-line />
-            </el-col>
-            <el-col
-              :xs="24"
-              :sm="6"
-            >
-              <dashboard-table />
-            </el-col>
-          </el-row>
+
+    <el-row :gutter="16" class="stat-row">
+      <el-col :span="6">
+        <el-card shadow="hover">
+          <el-statistic title="车位余量">
+            <template #default>{{ data.used_spaces }} / {{ data.total_spaces }}</template>
+            <template #suffix>
+              <span :style="{ color: data.total_spaces > 0 && data.used_spaces >= data.total_spaces ? '#f56c6c' : '#67c23a' }">车位</span>
+            </template>
+          </el-statistic>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="hover">
+          <el-statistic title="在场车辆" :value="data.in_park">
+            <template #suffix>台</template>
+          </el-statistic>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="hover">
+          <el-statistic title="今日收入" :value="data.today_income" prefix="¥" :precision="2" />
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="hover">
+          <el-statistic title="进 / 出" :value="data.today_entry">
+            <template #suffix>/ {{ data.today_exit }}</template>
+          </el-statistic>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <el-card class="device-card">
+      <template #header>设备状态</template>
+      <div class="device-list">
+        <div v-for="d in data.devices" :key="d.channel_name" class="device-item">
+          <el-tag :type="d.device_status === 'online' ? 'success' : 'danger'" size="small">
+            {{ d.device_status === 'online' ? '在线' : '离线' }}
+          </el-tag>
+          <span class="device-label">{{ d.channel_name }}</span>
+          <span class="device-name">{{ d.device_name }} ({{ d.direction === 'in' ? '入口' : '出口' }})</span>
         </div>
+        <el-empty v-if="!data.devices || data.devices.length === 0" description="暂无设备" :image-size="60" />
       </div>
-    </div>
+    </el-card>
   </div>
 </template>
 
 <script setup>
-import EchartsLine from '@/view/dashboard/dashboardCharts/echartsLine.vue'
-import DashboardTable from '@/view/dashboard/dashboardTable/dashboardTable.vue'
-import { ref } from 'vue'
-import { useRouter } from 'vue-router'
-import { useWeatherInfo } from '@/view/dashboard/weather.js'
-import SelectImage from '@/components/selectImage/selectImage.vue'
+import { ref, reactive, onMounted, onUnmounted } from 'vue'
+import { getMonitor } from '@/api/dashboard'
 
-defineOptions({
-  name: 'Dashboard'
+const data = reactive({
+  total_spaces: 0, used_spaces: 0, in_park: 0,
+  today_income: 0, today_entry: 0, today_exit: 0,
+  devices: []
 })
+const now = ref('')
+let timer = null
 
-const weatherInfo = useWeatherInfo()
-
-const toolCards = ref([
-  {
-    label: '用户管理',
-    icon: 'monitor',
-    name: 'user',
-    color: '#ff9c6e',
-    bg: 'rgba(255, 156, 110,.3)'
-  },
-  {
-    label: '角色管理',
-    icon: 'setting',
-    name: 'authority',
-    color: '#69c0ff',
-    bg: 'rgba(105, 192, 255,.3)'
-  },
-  {
-    label: '菜单管理',
-    icon: 'menu',
-    name: 'menu',
-    color: '#b37feb',
-    bg: 'rgba(179, 127, 235,.3)'
-  },
-  {
-    label: '代码生成器',
-    icon: 'cpu',
-    name: 'autoCode',
-    color: '#ffd666',
-    bg: 'rgba(255, 214, 102,.3)'
-  },
-  {
-    label: '表单生成器',
-    icon: 'document-checked',
-    name: 'formCreate',
-    color: '#ff85c0',
-    bg: 'rgba(255, 133, 192,.3)'
-  },
-  {
-    label: '关于我们',
-    icon: 'user',
-    name: 'about',
-    color: '#5cdbd3',
-    bg: 'rgba(92, 219, 211,.3)'
-  }
-])
-
-const router = useRouter()
+function updateTime() {
+  now.value = new Date().toLocaleString('zh-CN')
+}
 
-const toTarget = (name) => {
-  router.push({ name })
+async function fetchData() {
+  try {
+    const res = await getMonitor()
+    if (res.code === 0) Object.assign(data, res.data)
+  } catch (e) { /* keep last data */ }
 }
 
-</script>
+onMounted(() => {
+  updateTime()
+  fetchData()
+  timer = setInterval(() => { updateTime(); fetchData() }, 10000)
+})
 
-<style lang="scss" scoped>
-.page {
-    @apply p-0;
-    .gva-card-box{
-      @apply p-4;
-      &+.gva-card-box{
-        @apply pt-0;
-      }
-    }
-    .gva-card {
-      @apply box-border bg-white rounded h-auto px-6 py-8 overflow-hidden shadow-sm;
-      .gva-card-title{
-        @apply pb-5 border-t-0 border-l-0 border-r-0 border-b border-solid border-gray-100;
-      }
-    }
-    .gva-top-card {
-        @apply h-72 flex items-center justify-between text-gray-500;
-        &-left {
-          @apply h-full flex flex-col w-auto;
-            &-title {
-              @apply text-3xl text-gray-600;
-            }
-            &-dot {
-              @apply mt-4 text-gray-600 text-lg;
-            }
-            &-item{
-              +.gva-top-card-left-item{
-                margin-top: 24px;
-              }
-              margin-top: 14px;
-            }
-        }
-        &-right {
-            height: 600px;
-            width: 600px;
-            margin-top: 28px;
-        }
-    }
-     ::v-deep(.el-card__header){
-          @apply p-0  border-gray-200;
-        }
-        .card-header{
-          @apply pb-5 border-b border-solid border-gray-200 border-t-0 border-l-0 border-r-0;
-        }
-    .quick-entrance-items {
-      @apply flex items-center justify-center text-center text-gray-800;
-        .quick-entrance-item {
-          @apply px-8 py-6 flex items-center flex-col transition-all duration-100 ease-in-out rounded-lg cursor-pointer;
-          &:hover{
-            @apply shadow-lg;
-          }
-            &-icon {
-              @apply flex items-center h-16 w-16 rounded-lg justify-center mx-0 my-auto text-2xl;
-            }
-            p {
-                @apply mt-2.5;
-            }
-        }
-    }
-}
-.dashboard-icon {
-  @apply flex items-center text-xl mr-2 text-blue-400;
-}
+onUnmounted(() => clearInterval(timer))
+</script>
 
+<style scoped>
+.monitor-page { padding: 16px; }
+.monitor-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
+.monitor-header h2 { margin: 0; font-size: 22px; }
+.time { color: #909399; font-size: 14px; }
+.stat-row { margin-bottom: 16px; }
+.stat-row .el-card { text-align: center; }
+.device-card { margin-top: 0; }
+.device-list { display: flex; flex-wrap: wrap; gap: 12px; }
+.device-item { display: flex; align-items: center; gap: 8px; padding: 8px 14px; background: #f5f7fa; border-radius: 6px; }
+.device-label { font-weight: 500; }
+.device-name { color: #909399; font-size: 13px; }
 </style>