Procházet zdrojové kódy

fix(big-screen): 严格按参考图重写大屏 UI 布局

- 顶部: 6 张业务卡片横向一线
- 第二行: 2 饼图(人员岗位/事故类型) + 1 双折线图(巡检抽检)
- 第三行: 1 柱状图(客诉月度) + 1 走马灯(预警动态)
- 暗色主题 + 渐变标题 + 时钟实时
yangjingjing před 2 dny
rodič
revize
3e480072cc
1 změnil soubory, kde provedl 273 přidání a 318 odebrání
  1. 273 318
      src/views/risk/bigScreen/index.vue

+ 273 - 318
src/views/risk/bigScreen/index.vue

@@ -1,146 +1,146 @@
 <!--
   3.3 风控品控平台大屏
-  - ① 6 张业务数据卡片
-  - ② 2 个饼图(人员岗位 + 事故类型)
-  - ③ 1 个双折线面积图(巡检/抽检近 30 天)
-  - ④ 1 个渐变柱状图(客诉月度)
-  - ⑤ 1 个滚动走马灯(最新预警)
+  严格按参考图布局:
+   - 顶部: 6 张卡片一线
+   - 第二行: 2 饼图 + 1 双折线
+   - 第三行: 1 柱状图 + 1 走马灯
 -->
 <template>
   <div class="big-screen">
-    <!-- 顶部标题 -->
+    <!-- 顶部标题 -->
     <div class="bs-header">
-      <div class="bs-header-left">
+      <div class="bs-title-wrap">
+        <div class="bs-title-zh">企业安全风控管理平台</div>
+        <div class="bs-title-en">RISK & QUALITY CONTROL PLATFORM</div>
+      </div>
+      <div class="bs-clock">
         <div class="bs-time">{{ currentTime }}</div>
         <div class="bs-date">{{ currentDate }}</div>
       </div>
-      <div class="bs-header-title">
-        <div class="bs-title-zh">风控品控管理平台</div>
-        <div class="bs-title-en">RISK & QUALITY CONTROL PLATFORM</div>
+    </div>
+
+    <!-- 顶部: 6 张业务数据卡片 -->
+    <div class="bs-cards-row">
+      <div class="bs-card bs-card-1">
+        <div class="bs-card-head">
+          <el-icon class="bs-card-icon"><Warning /></el-icon>
+          <span class="bs-card-title">安全隐患</span>
+        </div>
+        <div class="bs-card-value">{{ fmtNum(card.dangerTotal) }}</div>
+        <div class="bs-card-stats">
+          <span class="stat-success">已处理 <em>{{ fmtNum(card.dangerHandled) }}</em></span>
+          <span class="stat-danger">未处理 <em>{{ fmtNum(card.dangerPending) }}</em></span>
+        </div>
       </div>
-      <div class="bs-header-right">
-        <el-button class="bs-refresh" type="primary" @click="loadAll">
-          <el-icon><Refresh /></el-icon>
-          <span style="margin-left: 4px">刷新数据</span>
-        </el-button>
+
+      <div class="bs-card bs-card-2">
+        <div class="bs-card-head">
+          <el-icon class="bs-card-icon"><Aim /></el-icon>
+          <span class="bs-card-title">危险源</span>
+        </div>
+        <div class="bs-card-value">{{ fmtNum(card.dangerSourceTotal) }}</div>
+        <div class="bs-card-stats">
+          <span class="stat-info">危险源总数</span>
+          <span class="stat-danger">本月新增 <em>{{ fmtNum(card.dangerSourceMonthNew) }}</em></span>
+        </div>
       </div>
-    </div>
 
-    <!-- 主体三列布局 -->
-    <div class="bs-body">
-      <!-- 左列:人员岗位 + 巡检/抽检 -->
-      <div class="bs-col bs-col-left">
-        <div class="bs-panel">
-          <div class="bs-panel-title">
-            <span class="bs-title-bar"></span>人员岗位占比
-          </div>
-          <div ref="personnelPieRef" class="bs-chart bs-pie-chart"></div>
+      <div class="bs-card bs-card-3">
+        <div class="bs-card-head">
+          <el-icon class="bs-card-icon"><Document /></el-icon>
+          <span class="bs-card-title">作业上报</span>
         </div>
-        <div class="bs-panel">
-          <div class="bs-panel-title">
-            <span class="bs-title-bar"></span>巡检/抽检 · 近30天趋势
-          </div>
-          <div ref="trendLineRef" class="bs-chart bs-line-chart"></div>
+        <div class="bs-card-value">{{ fmtNum(card.workReportTotal) }}</div>
+        <div class="bs-card-stats">
+          <span class="stat-info">上报总数</span>
+          <span class="stat-primary">作业进行中 <em>{{ fmtNum(card.workReportOngoing) }}</em></span>
         </div>
       </div>
 
-      <!-- 中列:6 张卡片 -->
-      <div class="bs-col bs-col-center">
-        <div class="bs-cards">
-          <div class="bs-card bs-card-danger">
-            <div class="bs-card-title">安全隐患</div>
-            <div class="bs-card-value">{{ fmtNum(card.dangerTotal) }}</div>
-            <div class="bs-card-meta">
-              <span class="meta-success">已闭环 {{ fmtNum(card.dangerHandled) }}</span>
-              <span class="meta-danger">待处理 {{ fmtNum(card.dangerPending) }}</span>
-            </div>
-          </div>
-
-          <div class="bs-card bs-card-source">
-            <div class="bs-card-title">危险源</div>
-            <div class="bs-card-value">{{ fmtNum(card.dangerSourceTotal) }}</div>
-            <div class="bs-card-meta">
-              <span class="meta-default">登记在册</span>
-              <span class="meta-danger">本月新增 {{ fmtNum(card.dangerSourceMonthNew) }}</span>
-            </div>
-          </div>
-
-          <div class="bs-card bs-card-work">
-            <div class="bs-card-title">作业上报</div>
-            <div class="bs-card-value">{{ fmtNum(card.workReportTotal) }}</div>
-            <div class="bs-card-meta">
-              <span class="meta-default">历史累计</span>
-              <span class="meta-primary">进行中 {{ fmtNum(card.workReportOngoing) }}</span>
-            </div>
-          </div>
-
-          <div class="bs-card bs-card-audit">
-            <div class="bs-card-title">审计工作 · 人员异动</div>
-            <div class="bs-card-value">{{ fmtNum(card.personnelChangeTotal) }}</div>
-            <div class="bs-card-meta">
-              <span class="meta-danger">离职 {{ fmtNum(card.personnelLeave) }}</span>
-              <span class="meta-primary">调任 {{ fmtNum(card.personnelTransfer) }}</span>
-            </div>
-          </div>
-
-          <div class="bs-card bs-card-equip">
-            <div class="bs-card-title">特种设备</div>
-            <div class="bs-card-value">{{ fmtNum(card.specialEquipmentTotal) }}</div>
-            <div class="bs-card-meta">
-              <span class="meta-success">运行中 {{ fmtNum(card.specialEquipmentRunning) }}</span>
-              <span class="meta-warning">维护中 {{ fmtNum(card.specialEquipmentMaintaining) }}</span>
-            </div>
-          </div>
-
-          <div class="bs-card bs-card-visit">
-            <div class="bs-card-title">访厂审核</div>
-            <div class="bs-card-value">{{ fmtNum(card.factoryVisitTotal) }}</div>
-            <div class="bs-card-meta">
-              <span class="meta-success">通过 {{ fmtNum(card.factoryVisitPassed) }}</span>
-              <span class="meta-danger">不通过 {{ fmtNum(card.factoryVisitRejected) }}</span>
-            </div>
-          </div>
+      <div class="bs-card bs-card-4">
+        <div class="bs-card-head">
+          <el-icon class="bs-card-icon"><User /></el-icon>
+          <span class="bs-card-title">审计工作</span>
+        </div>
+        <div class="bs-card-value">{{ fmtNum(card.personnelChangeTotal) }}</div>
+        <div class="bs-card-stats">
+          <span class="stat-danger">离职人数 <em>{{ fmtNum(card.personnelLeave) }}</em></span>
+          <span class="stat-primary">调任人数 <em>{{ fmtNum(card.personnelTransfer) }}</em></span>
+        </div>
+      </div>
+
+      <div class="bs-card bs-card-5">
+        <div class="bs-card-head">
+          <el-icon class="bs-card-icon"><Tools /></el-icon>
+          <span class="bs-card-title">特种设备</span>
+        </div>
+        <div class="bs-card-value">{{ fmtNum(card.specialEquipmentTotal) }}</div>
+        <div class="bs-card-stats">
+          <span class="stat-success">运行数 <em>{{ fmtNum(card.specialEquipmentRunning) }}</em></span>
+          <span class="stat-warning">维护数 <em>{{ fmtNum(card.specialEquipmentMaintaining) }}</em></span>
         </div>
       </div>
 
-      <!-- 右列:事故 + 客诉 -->
-      <div class="bs-col bs-col-right">
-        <div class="bs-panel">
-          <div class="bs-panel-title">
-            <span class="bs-title-bar"></span>事故类型分布
-          </div>
-          <div ref="accidentPieRef" class="bs-chart bs-pie-chart"></div>
+      <div class="bs-card bs-card-6">
+        <div class="bs-card-head">
+          <el-icon class="bs-card-icon"><Shop /></el-icon>
+          <span class="bs-card-title">访厂审核</span>
         </div>
-        <div class="bs-panel">
-          <div class="bs-panel-title">
-            <span class="bs-title-bar"></span>客诉统计 · 近12个月
-          </div>
-          <div ref="complaintBarRef" class="bs-chart bs-bar-chart"></div>
+        <div class="bs-card-value">{{ fmtNum(card.factoryVisitTotal) }}</div>
+        <div class="bs-card-stats">
+          <span class="stat-success">审核通过数 <em>{{ fmtNum(card.factoryVisitPassed) }}</em></span>
+          <span class="stat-danger">审核不通过 <em>{{ fmtNum(card.factoryVisitRejected) }}</em></span>
         </div>
       </div>
     </div>
 
-    <!-- 底部:预警走马灯 -->
-    <div class="bs-footer">
-      <div class="bs-footer-title">
-        <span class="bs-title-bar"></span>最新预警事件
-        <span class="bs-footer-tag">实时刷新</span>
+    <!-- 第二行: 2 饼图 + 1 双折线 -->
+    <div class="bs-row bs-row-charts">
+      <div class="bs-panel">
+        <div class="bs-panel-title">
+          <span class="bs-title-bar"></span>人员岗位占比
+        </div>
+        <div ref="personnelPieRef" class="bs-chart"></div>
+      </div>
+      <div class="bs-panel">
+        <div class="bs-panel-title">
+          <span class="bs-title-bar"></span>事故类型分布
+        </div>
+        <div ref="accidentPieRef" class="bs-chart"></div>
       </div>
-      <div class="bs-stream">
-        <el-scrollbar height="100%">
-          <div class="bs-stream-list">
-            <div v-for="w in warningStream" :key="w.id" class="bs-stream-item">
-              <span class="bs-dot" :style="{ background: dotColor(w.levelColor) }"></span>
-              <span class="bs-stream-time">{{ w.releaseTime }}</span>
-              <span class="bs-stream-level" :style="{ color: dotColor(w.levelColor) }">
-                [{{ w.warningLevel || '一般' }}]
-              </span>
-              <span class="bs-stream-title">{{ w.title }}</span>
-              <span class="bs-stream-store">{{ w.storeName || '—' }}</span>
+      <div class="bs-panel bs-panel-wide">
+        <div class="bs-panel-title">
+          <span class="bs-title-bar"></span>近一月巡检/抽检趋势
+        </div>
+        <div ref="trendLineRef" class="bs-chart"></div>
+      </div>
+    </div>
+
+    <!-- 第三行: 1 柱状图 + 1 走马灯 -->
+    <div class="bs-row bs-row-bottom">
+      <div class="bs-panel bs-panel-wide">
+        <div class="bs-panel-title">
+          <span class="bs-title-bar"></span>近一年客诉数量统计
+        </div>
+        <div ref="complaintBarRef" class="bs-chart"></div>
+      </div>
+      <div class="bs-panel bs-panel-stream">
+        <div class="bs-panel-title">
+          <span class="bs-title-bar"></span>最新预警动态
+          <span class="bs-stream-tag">实时刷新</span>
+        </div>
+        <div class="bs-stream">
+          <el-scrollbar height="100%">
+            <div class="bs-stream-list">
+              <div v-for="w in warningStream" :key="w.id" class="bs-stream-item">
+                <span class="bs-stream-time">{{ w.releaseTime }}</span>
+                <span class="bs-dot" :style="{ background: dotColor(w.levelColor) }"></span>
+                <span class="bs-stream-content">{{ w.title }}</span>
+              </div>
+              <div v-if="!warningStream.length" class="bs-stream-empty">暂无预警</div>
             </div>
-            <div v-if="!warningStream.length" class="bs-stream-empty">暂无预警</div>
-          </div>
-        </el-scrollbar>
+          </el-scrollbar>
+        </div>
       </div>
     </div>
   </div>
@@ -149,7 +149,14 @@
 <script setup lang="ts">
 import { onBeforeUnmount, onMounted, ref } from 'vue';
 import * as echarts from 'echarts';
-import { Refresh } from '@element-plus/icons-vue';
+import {
+  Aim,
+  Document,
+  Shop,
+  Tools,
+  User,
+  Warning
+} from '@element-plus/icons-vue';
 import {
   getBigScreenCards,
   getBigScreenCharts,
@@ -197,7 +204,7 @@ let timer: number | undefined;
 let streamTimer: number | undefined;
 let resizeHandler: (() => void) | undefined;
 
-// ===== 工具函数 =====
+// ===== 工具 =====
 const fmtNum = (n: number | undefined | null): string => {
   if (n === undefined || n === null) return '0';
   return n.toLocaleString('zh-CN');
@@ -209,7 +216,7 @@ const dotColor = (c?: string): string => {
   return '#1890ff';
 };
 
-// ===== 加载数据 =====
+// ===== 加载 =====
 async function loadCards() {
   try {
     const res = await getBigScreenCards();
@@ -234,7 +241,7 @@ async function loadCharts() {
 
 async function loadStream() {
   try {
-    const res = await getBigScreenWarnings(15);
+    const res = await getBigScreenWarnings(20);
     const list: BigScreenWarningStreamVo[] = (res as any).data ?? res;
     warningStream.value = Array.isArray(list) ? list : [];
   } catch (e) {
@@ -268,10 +275,10 @@ function renderPersonnelPie(data: { name: string; value: number }[]) {
   personnelPieChart.setOption({
     tooltip: {
       trigger: 'item',
-      formatter: (params: any) => `${params.marker} ${params.name}<br/>${params.value} 人 (${params.percent}%)`,
       backgroundColor: 'rgba(20,40,80,0.95)',
       borderColor: '#3a8dff',
-      textStyle: { color: '#fff' }
+      textStyle: { color: '#fff' },
+      formatter: '{b}: {c} ({d}%)'
     },
     legend: {
       type: 'scroll',
@@ -285,20 +292,12 @@ function renderPersonnelPie(data: { name: string; value: number }[]) {
     series: [
       {
         type: 'pie',
-        radius: ['45%', '70%'],
+        radius: ['50%', '75%'],
         center: ['50%', '42%'],
         avoidLabelOverlap: true,
-        itemStyle: {
-          borderColor: '#0a1a3a',
-          borderWidth: 2
-        },
-        label: {
-          show: true,
-          color: '#aac8ff',
-          formatter: '{b}\n{d}%',
-          fontSize: 11
-        },
-        labelLine: { lineStyle: { color: '#3a8dff' } },
+        itemStyle: { borderColor: '#0a1a3a', borderWidth: 2 },
+        label: { show: false },
+        labelLine: { show: false },
         data: data.length ? data : [{ name: '暂无数据', value: 0 }]
       }
     ]
@@ -315,7 +314,8 @@ function renderAccidentPie(data: { name: string; value: number }[]) {
       trigger: 'item',
       backgroundColor: 'rgba(20,40,80,0.95)',
       borderColor: '#3a8dff',
-      textStyle: { color: '#fff' }
+      textStyle: { color: '#fff' },
+      formatter: '{b}: {c} ({d}%)'
     },
     legend: {
       type: 'scroll',
@@ -327,12 +327,12 @@ function renderAccidentPie(data: { name: string; value: number }[]) {
     series: [
       {
         type: 'pie',
-        radius: ['45%', '70%'],
+        radius: ['50%', '75%'],
         center: ['50%', '42%'],
         avoidLabelOverlap: true,
         itemStyle: { borderColor: '#0a1a3a', borderWidth: 2 },
-        label: { color: '#aac8ff', formatter: '{b}\n{d}%', fontSize: 11 },
-        labelLine: { lineStyle: { color: '#3a8dff' } },
+        label: { show: false },
+        labelLine: { show: false },
         data: data.length ? data : [{ name: '暂无数据', value: 0 }]
       }
     ]
@@ -354,12 +354,14 @@ function renderTrendLine(dates: string[], quality: number[], risk: number[]) {
     legend: {
       data: ['品控巡检', '风控抽检'],
       top: 8,
+      right: 16,
       textStyle: { color: '#aac8ff' }
     },
-    grid: { left: 40, right: 30, top: 40, bottom: 40 },
+    grid: { left: 40, right: 24, top: 40, bottom: 30 },
     xAxis: {
       type: 'category',
       data: dates,
+      boundaryGap: false,
       axisLine: { lineStyle: { color: '#3a8dff' } },
       axisLabel: { color: '#aac8ff', fontSize: 10 }
     },
@@ -419,12 +421,12 @@ function renderComplaintBar(months: string[], counts: number[]) {
       borderColor: '#3a8dff',
       textStyle: { color: '#fff' }
     },
-    grid: { left: 40, right: 20, top: 30, bottom: 40 },
+    grid: { left: 40, right: 16, top: 30, bottom: 30 },
     xAxis: {
       type: 'category',
       data: months,
       axisLine: { lineStyle: { color: '#3a8dff' } },
-      axisLabel: { color: '#aac8ff', fontSize: 10, interval: 0 }
+      axisLabel: { color: '#aac8ff', fontSize: 11 }
     },
     yAxis: {
       type: 'value',
@@ -440,15 +442,9 @@ function renderComplaintBar(months: string[], counts: number[]) {
         itemStyle: {
           color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
             { offset: 0, color: '#5b8ff9' },
-            { offset: 1, color: 'rgba(91,143,249,0.2)' }
+            { offset: 1, color: 'rgba(91,143,249,0.15)' }
           ]),
           borderRadius: [4, 4, 0, 0]
-        },
-        label: {
-          show: true,
-          position: 'top',
-          color: '#aac8ff',
-          fontSize: 11
         }
       }
     ]
@@ -470,7 +466,6 @@ onMounted(() => {
   };
   window.addEventListener('resize', resizeHandler);
 
-  // 给图表初始化一点时间,以便容器有正确尺寸
   setTimeout(() => {
     personnelPieChart?.resize();
     accidentPieChart?.resize();
@@ -501,150 +496,85 @@ onBeforeUnmount(() => {
 <style scoped lang="scss">
 .big-screen {
   width: 100%;
-  height: 100%;
   min-height: 100vh;
-  background: radial-gradient(ellipse at center, #0a1a3a 0%, #050b1f 100%);
+  background: radial-gradient(ellipse at top, #0a1a3a 0%, #050b1f 60%, #02060f 100%);
   color: #e6f0ff;
   display: flex;
   flex-direction: column;
-  padding: 12px;
+  padding: 16px;
   box-sizing: border-box;
   font-family: 'Microsoft YaHei', 'PingFang SC', Arial, sans-serif;
 }
 
-// ===== 顶部 =====
+// ===== 顶部标题栏 =====
 .bs-header {
-  height: 70px;
+  height: 60px;
   display: flex;
   align-items: center;
   justify-content: space-between;
-  border-bottom: 1px solid rgba(58, 141, 255, 0.3);
-  margin-bottom: 12px;
-
-  &-left,
-  &-right {
-    width: 320px;
-    display: flex;
-    align-items: center;
-    color: #aac8ff;
-  }
-
-  &-right {
-    justify-content: flex-end;
-  }
+  margin-bottom: 16px;
+  padding: 0 8px;
 
-  &-title {
+  .bs-title-wrap {
     flex: 1;
     text-align: center;
   }
-}
 
-.bs-time {
-  font-size: 28px;
-  font-weight: 600;
-  color: #5ad8ff;
-  text-shadow: 0 0 10px rgba(90, 216, 255, 0.6);
-}
+  .bs-title-zh {
+    font-size: 28px;
+    font-weight: 700;
+    background: linear-gradient(180deg, #ffffff, #5ad8ff);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    letter-spacing: 4px;
+  }
 
-.bs-date {
-  margin-left: 12px;
-  font-size: 14px;
-  color: #aac8ff;
-}
+  .bs-title-en {
+    font-size: 12px;
+    color: #5ad8ff;
+    letter-spacing: 2px;
+    margin-top: 4px;
+  }
 
-.bs-title-zh {
-  font-size: 30px;
-  font-weight: 700;
-  background: linear-gradient(180deg, #ffffff, #5ad8ff);
-  -webkit-background-clip: text;
-  -webkit-text-fill-color: transparent;
-  letter-spacing: 4px;
-}
+  .bs-clock {
+    position: absolute;
+    right: 24px;
+    text-align: right;
+  }
 
-.bs-title-en {
-  font-size: 12px;
-  color: #5ad8ff;
-  letter-spacing: 2px;
-  margin-top: 4px;
-}
+  .bs-time {
+    font-size: 22px;
+    font-weight: 600;
+    color: #5ad8ff;
+    text-shadow: 0 0 10px rgba(90, 216, 255, 0.6);
+  }
 
-.bs-refresh {
-  background: linear-gradient(180deg, #1a4a8c, #0a2855) !important;
-  border: 1px solid #3a8dff !important;
+  .bs-date {
+    font-size: 12px;
+    color: #aac8ff;
+    margin-top: 2px;
+  }
 }
 
-// ===== 三列布局 =====
-.bs-body {
-  flex: 1;
+// ===== 卡片行 =====
+.bs-cards-row {
   display: grid;
-  grid-template-columns: 1fr 1.2fr 1fr;
-  gap: 12px;
-  min-height: 0;
-}
-
-.bs-col {
-  display: flex;
-  flex-direction: column;
+  grid-template-columns: repeat(6, 1fr);
   gap: 12px;
-  min-height: 0;
+  margin-bottom: 16px;
 }
 
-.bs-panel {
-  flex: 1;
-  background: linear-gradient(180deg, rgba(20, 40, 80, 0.5), rgba(10, 26, 58, 0.5));
+.bs-card {
+  background: linear-gradient(135deg, rgba(20, 40, 80, 0.7), rgba(10, 26, 58, 0.5));
   border: 1px solid rgba(58, 141, 255, 0.25);
   border-radius: 6px;
-  padding: 12px;
+  padding: 14px 16px;
   display: flex;
   flex-direction: column;
-  min-height: 0;
-}
-
-.bs-panel-title {
-  font-size: 16px;
-  font-weight: 600;
-  color: #aac8ff;
-  padding-bottom: 8px;
-  border-bottom: 1px solid rgba(58, 141, 255, 0.2);
-  display: flex;
-  align-items: center;
-}
-
-.bs-title-bar {
-  display: inline-block;
-  width: 4px;
-  height: 14px;
-  background: linear-gradient(180deg, #5ad8ff, #3a8dff);
-  margin-right: 8px;
-  border-radius: 2px;
-}
-
-.bs-chart {
-  flex: 1;
-  width: 100%;
-  min-height: 0;
-}
-
-// ===== 卡片区 =====
-.bs-cards {
-  display: grid;
-  grid-template-columns: 1fr 1fr;
-  grid-template-rows: repeat(3, 1fr);
-  gap: 12px;
-  height: 100%;
-  min-height: 0;
-}
-
-.bs-card {
-  border-radius: 6px;
-  padding: 16px;
+  gap: 8px;
   position: relative;
   overflow: hidden;
-  display: flex;
-  flex-direction: column;
-  justify-content: space-between;
-  border: 1px solid rgba(58, 141, 255, 0.3);
-  background: linear-gradient(135deg, rgba(20, 40, 80, 0.7), rgba(10, 26, 58, 0.5));
+  min-height: 110px;
 
   &::before {
     content: '';
@@ -652,16 +582,27 @@ onBeforeUnmount(() => {
     top: 0;
     left: 0;
     right: 0;
-    height: 3px;
+    height: 2px;
     background: linear-gradient(90deg, transparent, #5ad8ff, transparent);
     opacity: 0.6;
   }
 }
 
-.bs-card-title {
-  font-size: 14px;
+.bs-card-head {
+  display: flex;
+  align-items: center;
+  gap: 8px;
   color: #aac8ff;
-  margin-bottom: 8px;
+  font-size: 14px;
+}
+
+.bs-card-icon {
+  font-size: 18px;
+  color: #5ad8ff;
+}
+
+.bs-card-title {
+  font-weight: 500;
 }
 
 .bs-card-value {
@@ -670,62 +611,92 @@ onBeforeUnmount(() => {
   color: #ffffff;
   text-shadow: 0 0 12px rgba(90, 216, 255, 0.4);
   font-family: 'DIN Alternate', 'Microsoft YaHei', monospace;
+  line-height: 1.1;
+  text-align: center;
+  margin: 4px 0;
 }
 
-.bs-card-meta {
+.bs-card-stats {
   display: flex;
   justify-content: space-between;
   font-size: 12px;
-  margin-top: 8px;
-}
 
-.meta-success {
-  color: #5ad8a6;
-  font-weight: 600;
+  span em {
+    font-style: normal;
+    font-weight: 600;
+    margin-left: 2px;
+  }
 }
 
-.meta-danger {
-  color: #ff4d4f;
-  font-weight: 600;
-}
+.stat-success { color: #5ad8a6; em { color: #5ad8a6; } }
+.stat-danger  { color: #ff4d4f; em { color: #ff4d4f; } }
+.stat-warning { color: #faad14; em { color: #faad14; } }
+.stat-primary { color: #5ad8ff; em { color: #5ad8ff; } }
+.stat-info    { color: #aac8ff; }
 
-.meta-warning {
-  color: #faad14;
-  font-weight: 600;
+// ===== 行 =====
+.bs-row {
+  display: grid;
+  gap: 12px;
+  margin-bottom: 16px;
 }
 
-.meta-primary {
-  color: #5ad8ff;
-  font-weight: 600;
+.bs-row-charts {
+  grid-template-columns: 1fr 1fr 1.4fr;
+  height: 320px;
 }
 
-.meta-default {
-  color: #aac8ff;
+.bs-row-bottom {
+  grid-template-columns: 1.4fr 1fr;
+  height: 320px;
 }
 
-// ===== 底部走马灯 =====
-.bs-footer {
-  height: 140px;
+.bs-panel {
   background: linear-gradient(180deg, rgba(20, 40, 80, 0.5), rgba(10, 26, 58, 0.5));
   border: 1px solid rgba(58, 141, 255, 0.25);
   border-radius: 6px;
-  padding: 8px 16px;
-  margin-top: 12px;
+  padding: 12px 16px;
   display: flex;
   flex-direction: column;
+  min-width: 0;
+  overflow: hidden;
 }
 
-.bs-footer-title {
-  font-size: 16px;
+.bs-panel-title {
+  font-size: 15px;
   font-weight: 600;
   color: #aac8ff;
-  padding-bottom: 6px;
+  padding-bottom: 8px;
   border-bottom: 1px solid rgba(58, 141, 255, 0.2);
   display: flex;
   align-items: center;
+  margin-bottom: 4px;
+}
+
+.bs-title-bar {
+  display: inline-block;
+  width: 4px;
+  height: 14px;
+  background: linear-gradient(180deg, #5ad8ff, #3a8dff);
+  margin-right: 8px;
+  border-radius: 2px;
+}
+
+.bs-chart {
+  flex: 1;
+  min-height: 0;
+  width: 100%;
+}
+
+// ===== 走马灯 =====
+.bs-stream {
+  flex: 1;
+  min-height: 0;
+  margin-top: 4px;
+  overflow: hidden;
 }
 
-.bs-footer-tag {
+.bs-stream-tag {
   margin-left: auto;
   font-size: 12px;
   color: #5ad8a6;
@@ -735,12 +706,6 @@ onBeforeUnmount(() => {
   border-radius: 10px;
 }
 
-.bs-stream {
-  flex: 1;
-  min-height: 0;
-  margin-top: 4px;
-}
-
 .bs-stream-list {
   display: flex;
   flex-direction: column;
@@ -751,7 +716,7 @@ onBeforeUnmount(() => {
   display: flex;
   align-items: center;
   gap: 10px;
-  padding: 6px 8px;
+  padding: 6px 10px;
   font-size: 13px;
   color: #e6f0ff;
   border-radius: 4px;
@@ -763,39 +728,29 @@ onBeforeUnmount(() => {
   }
 }
 
-.bs-dot {
-  display: inline-block;
-  width: 8px;
-  height: 8px;
-  border-radius: 50%;
-  box-shadow: 0 0 8px currentColor;
-}
-
 .bs-stream-time {
   color: #aac8ff;
   font-family: 'DIN Alternate', monospace;
   font-size: 12px;
-  min-width: 140px;
+  min-width: 150px;
 }
 
-.bs-stream-level {
-  font-weight: 600;
-  min-width: 60px;
+.bs-dot {
+  display: inline-block;
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  box-shadow: 0 0 8px currentColor;
+  flex-shrink: 0;
 }
 
-.bs-stream-title {
+.bs-stream-content {
   flex: 1;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
 }
 
-.bs-stream-store {
-  color: #5ad8a6;
-  font-size: 12px;
-  min-width: 100px;
-}
-
 .bs-stream-empty {
   text-align: center;
   color: #aac8ff;