Browse Source

update src/views/admin/Dashboard.vue

reghao 4 weeks ago
parent
commit
c6f5630124
1 changed files with 232 additions and 208 deletions
  1. 232 208
      src/views/admin/Dashboard.vue

+ 232 - 208
src/views/admin/Dashboard.vue

@@ -24,18 +24,13 @@
 
 
       <div class="banner-stats">
       <div class="banner-stats">
         <div class="stat-item">
         <div class="stat-item">
-          <div class="stat-label">巡检范围</div>
-          <div class="stat-value">{{ reportData.totalNodes || 0 }} <small>Nodes</small></div>
+          <div class="stat-label">巡检</div>
+          <div class="stat-value">{{ cardConfigs.length }}</div>
         </div>
         </div>
         <div class="stat-divider"></div>
         <div class="stat-divider"></div>
         <div class="stat-item">
         <div class="stat-item">
-          <div class="stat-label">检测耗时</div>
-          <div class="stat-value">{{ reportData.checkDuration || '0ms' }}</div>
-        </div>
-        <div class="stat-divider" v-if="hasAnyIssue"></div>
-        <div class="stat-item" v-if="hasAnyIssue">
-          <div class="stat-label">风险指数</div>
-          <div class="stat-value risk-text">{{ reportData.riskLevel || 'High' }}</div>
+          <div class="stat-label">风险项</div>
+          <div class="stat-value" :class="hasAnyIssue ? 'risk-text' : ''">{{ totalIssuesCount }}</div>
         </div>
         </div>
       </div>
       </div>
 
 
@@ -53,15 +48,36 @@
       </div>
       </div>
     </div>
     </div>
 
 
-    <div class="report-grid">
-      <el-row :gutter="20" class="force-row">
+    <div v-for="cat in categories" :key="cat" class="category-group">
+      <div class="category-header">
+        <div class="category-title-box">
+          <span class="category-name">{{ cat }}</span>
+          <el-badge
+            :value="getCategoryIssueCount(cat)"
+            :hidden="getCategoryIssueCount(cat) === 0"
+            type="danger"
+            is-dot
+            class="cat-badge"
+          />
+        </div>
+        <div class="category-line"></div>
+      </div>
 
 
-        <el-col :span="12" v-for="config in cardConfigs" :key="config.key" class="mb-20">
+      <el-row :gutter="24" class="card-grid-row">
+        <el-col
+          :xs="24" :sm="12" :md="8"
+          v-for="config in getCardsByCategory(cat)"
+          :key="config.key"
+          class="card-col"
+        >
           <el-card shadow="hover" class="nice-card" :class="getShadowClass(config)">
           <el-card shadow="hover" class="nice-card" :class="getShadowClass(config)">
             <div slot="header" class="card-header">
             <div slot="header" class="card-header">
               <div class="title-wrapper">
               <div class="title-wrapper">
                 <i :class="[config.icon, 'icon-accent', config.textColor]"></i>
                 <i :class="[config.icon, 'icon-accent', config.textColor]"></i>
-                <span class="title-text">{{ config.title }} <small>{{ config.subTitle }}</small></span>
+                <div class="text-group">
+                  <span class="main-title">{{ config.title }}</span>
+                  <span class="sub-title">{{ config.subTitle }}</span>
+                </div>
               </div>
               </div>
               <div class="header-ops">
               <div class="header-ops">
                 <el-tooltip content="排查建议" placement="top">
                 <el-tooltip content="排查建议" placement="top">
@@ -72,7 +88,7 @@
                     @click="showAdvice(config)"
                     @click="showAdvice(config)"
                   ></el-button>
                   ></el-button>
                 </el-tooltip>
                 </el-tooltip>
-                <el-tag v-if="getData(config.key).length > 0" :type="config.tagType" size="mini" effect="dark" class="status-tag">
+                <el-tag v-if="getData(config.key).length > 0" :type="config.tagType" size="mini" effect="dark">
                   {{ getData(config.key).length }}
                   {{ getData(config.key).length }}
                 </el-tag>
                 </el-tag>
               </div>
               </div>
@@ -80,10 +96,14 @@
 
 
             <div class="card-body scroll-style">
             <div class="card-body scroll-style">
               <template v-if="getData(config.key).length > 0">
               <template v-if="getData(config.key).length > 0">
-                <div v-for="(item, index) in getData(config.key)" :key="index" class="progress-item">
-                  <div class="info">
-                    <span class="name text-ellipsis">{{ item.instance || item.name }}</span>
-                    <span class="val" :class="config.textColor">{{ item.value }}{{ config.unit }}</span>
+                <div v-for="(item, index) in getData(config.key)" :key="index" class="metric-entry">
+                  <div class="entry-info">
+                    <span class="entry-name" :title="item.name || item.instance">
+                      {{ item.name || item.instance }}
+                    </span>
+                    <span class="entry-val" :class="config.textColor">
+                      {{ item.value }}<small>{{ config.unit }}</small>
+                    </span>
                   </div>
                   </div>
                   <el-progress
                   <el-progress
                     :percentage="config.isRatio ? (item.value > 100 ? 100 : item.value) : 100"
                     :percentage="config.isRatio ? (item.value > 100 ? 100 : item.value) : 100"
@@ -93,8 +113,9 @@
                   />
                   />
                 </div>
                 </div>
               </template>
               </template>
-              <div v-else class="status-placeholder">
-                <i class="el-icon-circle-check"></i><p>{{ config.emptyText }}</p>
+              <div v-else class="empty-state">
+                <i class="el-icon-circle-check"></i>
+                <p>{{ config.emptyText }}</p>
               </div>
               </div>
             </div>
             </div>
           </el-card>
           </el-card>
@@ -105,19 +126,18 @@
     <el-dialog
     <el-dialog
       :title="'💡 排查建议: ' + currentAdvice.title"
       :title="'💡 排查建议: ' + currentAdvice.title"
       :visible.sync="dialogVisible"
       :visible.sync="dialogVisible"
-      width="500px"
+      width="550px"
       custom-class="advice-dialog"
       custom-class="advice-dialog"
       append-to-body
       append-to-body
     >
     >
-      <div class="advice-content">
-        <div v-if="currentAdvice.text" class="markdown-body">
-          <i class="el-icon-guideAdvice"></i>
+      <div class="advice-container">
+        <div v-if="currentAdvice.text" class="advice-box">
           <p v-html="formatAdvice(currentAdvice.text)"></p>
           <p v-html="formatAdvice(currentAdvice.text)"></p>
         </div>
         </div>
-        <el-empty v-else description="暂无排查建议" :image-size="60"></el-empty>
+        <el-empty v-else description="暂无指引" :image-size="60"></el-empty>
       </div>
       </div>
-      <span slot="footer" class="dialog-footer">
-        <el-button type="primary" size="small" @click="dialogVisible = false">知道了</el-button>
+      <span slot="footer">
+        <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
       </span>
       </span>
     </el-dialog>
     </el-dialog>
   </div>
   </div>
@@ -132,50 +152,69 @@ export default {
       loading: false,
       loading: false,
       dialogVisible: false,
       dialogVisible: false,
       currentAdvice: { title: '', text: '' },
       currentAdvice: { title: '', text: '' },
+      // 业务分类
+      categories: ['计算资源', '内存指标', '存储文件', '网络协议', '基础环境'],
       reportData: {
       reportData: {
-        cpuThrottled: [], advice_cpuThrottled: '',
-        memRisk: [], advice_memRisk: '',
-        diskIo: [], advice_diskIo: '',
-        diskUsageRisk: [], advice_diskUsageRisk: '',
-        inodeRisk: [], advice_inodeRisk: '',
-        fdRisk: [], advice_fdRisk: '',
-        netEstMax: [], advice_netEstMax: '',
-        netTwMax: [], advice_netTwMax: '',
-        netOverflows: [], advice_netOverflows: '',
-        netDrops: [], advice_netDrops: '',
-        advices: {}
+        advices: {},
+        // 动态数据将通过接口覆盖
       },
       },
-      // 使用配置化方式渲染卡片,减少 HTML 冗余
+      // 完整的 22 个指标配置
       cardConfigs: [
       cardConfigs: [
-        { key: 'cpuThrottled', title: 'CPU 性能节流', subTitle: '24H Throttled', icon: 'el-icon-cpu', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: 's', isRatio: false, emptyText: '无节流限制' },
-        { key: 'memRisk', title: '内存水位线', subTitle: '> 85% Usage', icon: 'el-icon-box', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '%', isRatio: true, emptyText: '水位正常' },
-        { key: 'diskIo', title: '磁盘 I/O 响应', subTitle: 'Wait > 50ms', icon: 'el-icon-receiving', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: 'ms', isRatio: false, emptyText: 'IO 响应极快' },
-        { key: 'diskUsageRisk', title: '根分区空间', subTitle: '( / ) > 85%', icon: 'el-icon-pie-chart', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '%', isRatio: true, emptyText: '磁盘空间充沛' },
-        { key: 'inodeRisk', title: 'inode 使用率', subTitle: '> 80% Usage', icon: 'el-icon-files', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: '%', isRatio: true, emptyText: '索引节点充足' },
-        { key: 'fdRisk', title: '文件句柄 (FD)', subTitle: 'Usage Ratio', icon: 'el-icon-link', textColor: 'primary-text', tagType: 'primary', progColor: '#409eff', unit: '%', isRatio: true, emptyText: 'FD 占用正常' },
-        { key: 'netEstMax', title: 'TCP EST 状态数量', subTitle: 'TCP EST', icon: 'el-icon-warning-outline', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '次', isRatio: false, emptyText: '未超过阈值' },
-        { key: 'netTwMax', title: 'TCP TIME_WAIT 状态', subTitle: 'TCP TIME_WAIT', icon: 'el-icon-warning-outline', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '次', isRatio: false, emptyText: '未超过阈值' },
-        { key: 'netOverflows', title: 'TCP 全连接队列溢出', subTitle: 'Listen Overflow', icon: 'el-icon-warning-outline', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '次', isRatio: false, emptyText: '无队列溢出' },
-        { key: 'netDrops', title: 'TCP 数据包丢弃', subTitle: 'TCP Drops', icon: 'el-icon-circle-close', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '次', isRatio: false, emptyText: '无 TCP 丢弃' }
+        /* 计算资源 */
+        { category: '计算资源', key: 'cpuThrottled', title: 'CPU 性能节流', subTitle: '24H Throttled', icon: 'el-icon-cpu', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: 's', isRatio: false, emptyText: '无节流限制' },
+        { category: '计算资源', key: 'nodeLoadRisk', title: '节点负载饱和度', subTitle: 'Load5 / Cores', icon: 'el-icon-odometer', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: '', isRatio: false, emptyText: '系统负载极低' },
+        { category: '计算资源', key: 'contextSwitchRisk', title: '上下文切换', subTitle: 'Context Switches', icon: 'el-icon-refresh', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: '次/s', isRatio: false, emptyText: '调度平稳' },
+        { category: '计算资源', key: 'zombieRisk', title: '僵尸进程', subTitle: 'Z States', icon: 'el-icon-stopwatch', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: '个', isRatio: false, emptyText: '无僵尸进程' },
+
+        /* 内存指标 */
+        { category: '内存指标', key: 'memRisk', title: '内存水位线', subTitle: '> 85% Usage', icon: 'el-icon-box', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '%', isRatio: true, emptyText: '水位正常' },
+        { category: '内存指标', key: 'memPsiRisk', title: '内存紧缩压力', subTitle: 'PSI Stall %', icon: 'el-icon-timer', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '%', isRatio: true, emptyText: '无访问延迟' },
+        { category: '内存指标', key: 'oomEvents', title: 'OOM Kill 事件', subTitle: 'Kernel OOM', icon: 'el-icon-warning', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '次', isRatio: false, emptyText: '无内存强杀' },
+        { category: '内存指标', key: 'pidLimitRisk', title: 'PID 进程数限制', subTitle: 'Forks / Max', icon: 'el-icon-connection', textColor: 'primary-text', tagType: 'primary', progColor: '#409eff', unit: '%', isRatio: true, emptyText: '池空间充足' },
+
+        /* 存储文件 */
+        { category: '存储文件', key: 'diskUsageRisk', title: '根分区空间', subTitle: '( / ) Usage', icon: 'el-icon-pie-chart', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '%', isRatio: true, emptyText: '空间充沛' },
+        { category: '存储文件', key: 'diskPredictRisk', title: '磁盘存满预测', subTitle: 'Predict 24H', icon: 'el-icon-magic-stick', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '', isRatio: false, emptyText: '无写满风险' },
+        { category: '存储文件', key: 'inodeRisk', title: 'inode 使用率', subTitle: 'Index Nodes', icon: 'el-icon-files', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: '%', isRatio: true, emptyText: '索引节点充足' },
+        { category: '存储文件', key: 'diskIo', title: '磁盘 IO 响应', subTitle: 'IO Wait', icon: 'el-icon-receiving', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: 'ms', isRatio: false, emptyText: '响应极快' },
+        { category: '存储文件', key: 'fdRisk', title: '文件句柄 (FD)', subTitle: 'FD Ratio', icon: 'el-icon-link', textColor: 'primary-text', tagType: 'primary', progColor: '#409eff', unit: '%', isRatio: true, emptyText: '占用正常' },
+        { category: '存储文件', key: 'readOnlyFsRisk', title: '文件系统只读', subTitle: 'RO Status', icon: 'el-icon-lock', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '', isRatio: false, emptyText: '挂载正常' },
+
+        /* 网络协议 */
+        { category: '网络协议', key: 'conntrackRisk', title: '连接跟踪表', subTitle: 'Conntrack', icon: 'el-icon-attract', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '%', isRatio: true, emptyText: '状态健康' },
+        { category: '网络协议', key: 'netEstMax', title: 'TCP EST 数量', subTitle: 'Established', icon: 'el-icon-warning-outline', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '次', isRatio: false, emptyText: '未超限' },
+        { category: '网络协议', key: 'netTwMax', title: 'TCP TIME_WAIT', subTitle: 'Wait Status', icon: 'el-icon-warning-outline', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '次', isRatio: false, emptyText: '未超限' },
+        { category: '网络协议', key: 'netBandwidthRisk', title: '网卡带宽水位', subTitle: 'Traffic Ratio', icon: 'el-icon-sort', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: '%', isRatio: true, emptyText: '流量平稳' },
+        { category: '网络协议', key: 'tcpRetransRisk', title: 'TCP 重传率', subTitle: 'Retrans Ratio', icon: 'el-icon-refresh-left', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '%', isRatio: true, emptyText: '传输稳定' },
+        { category: '网络协议', key: 'netOverflows', title: 'TCP 队列溢出', subTitle: 'Listen Overflow', icon: 'el-icon-warning-outline', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '次', isRatio: false, emptyText: '无溢出' },
+        { category: '网络协议', key: 'netDrops', title: 'TCP 数据包丢弃', subTitle: 'Packet Drops', icon: 'el-icon-circle-close', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '次', isRatio: false, emptyText: '无丢弃' },
+
+        /* 基础环境 */
+        { category: '基础环境', key: 'clockSkewRisk', title: '时钟偏移 (NTP)', subTitle: 'Clock Offset', icon: 'el-icon-alarm-clock', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: 's', isRatio: false, emptyText: '同步精确' }
       ]
       ]
     }
     }
   },
   },
   computed: {
   computed: {
-    hasAnyIssue() {
-      return this.cardConfigs.some(c => this.reportData[c.key]?.length > 0)
+    totalIssuesCount() {
+      return this.cardConfigs.filter(c => this.getData(c.key).length > 0).length
     },
     },
+    hasAnyIssue() { return this.totalIssuesCount > 0 },
     overallStatus() { return this.hasAnyIssue ? 'warning' : 'success' },
     overallStatus() { return this.hasAnyIssue ? 'warning' : 'success' },
     overallDescription() {
     overallDescription() {
-      return this.hasAnyIssue ? '系统检测到部分节点异常,请点击卡片问号查看排查建议。' : '所有基础设施运行参数健康。'
+      return this.hasAnyIssue ? `当前环境存在异常项,请优先处理风险卡片。` : '核心基础设施所有参数均在安全范围内。'
     }
     }
   },
   },
   created() { this.loadReport() },
   created() { this.loadReport() },
   methods: {
   methods: {
+    getCardsByCategory(cat) {
+      return this.cardConfigs.filter(config => config.category === cat)
+    },
+    getCategoryIssueCount(cat) {
+      return this.getCardsByCategory(cat).filter(config => this.getData(config.key).length > 0).length
+    },
     getData(key) { return this.reportData[key] || [] },
     getData(key) { return this.reportData[key] || [] },
     getShadowClass(config) {
     getShadowClass(config) {
-      const hasData = this.getData(config.key).length > 0
-      if (!hasData) return ''
-      return config.tagType + '-shadow'
+      return this.getData(config.key).length > 0 ? config.tagType + '-shadow' : ''
     },
     },
     async loadReport() {
     async loadReport() {
       this.loading = true
       this.loading = true
@@ -189,225 +228,210 @@ export default {
     showAdvice(config) {
     showAdvice(config) {
       this.currentAdvice = {
       this.currentAdvice = {
         title: config.title,
         title: config.title,
-        text: this.reportData.advices[config.key] || '暂无详细排查指引,请联系运维团队。'
+        text: this.reportData.advices[config.key] || '暂无详细指引。'
       }
       }
       this.dialogVisible = true
       this.dialogVisible = true
     },
     },
-    formatAdvice(text) {
-      // 简单的换行转义,如果后端返回的是带换行的文本
-      return text.replace(/\n/g, '<br>')
-    }
+    formatAdvice(text) { return text ? text.replace(/\n/g, '<br>') : '' }
   }
   }
 }
 }
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-/* 保持原样式并添加以下内容 */
+/* 1. 基础容器与间距控制 */
+.monitor-report-container {
+  padding: 32px;
+  background-color: #f8fafc;
+  min-height: 100vh;
+}
+
+/* 分类组间距 */
+.category-group {
+  margin-bottom: 40px;
+  background: rgba(255, 255, 255, 0.4);
+  padding: 20px;
+  border-radius: 16px;
+}
 
 
-.header-ops {
+/* 分类头部标题 */
+.category-header {
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
+  margin-bottom: 24px;
+  padding: 0 4px;
 }
 }
 
 
-.advice-btn {
-  font-size: 18px;
-  color: #94a3b8;
-  margin-right: 8px;
-  padding: 0;
-  transition: color 0.2s;
+.category-title-box {
+  display: flex;
+  align-items: center;
+  background: #1e293b;
+  padding: 8px 16px;
+  border-radius: 8px;
+  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
 }
 }
 
 
-.advice-btn:hover {
-  color: #409eff;
+.category-name {
+  color: #f8fafc;
+  font-weight: 700;
+  font-size: 15px;
+  letter-spacing: 0.5px;
 }
 }
 
 
-/* 弹窗样式美化 */
-.advice-dialog >>> .el-dialog__header {
-  border-bottom: 1px solid #f1f5f9;
-  padding: 20px;
+.category-line {
+  flex: 1;
+  height: 2px;
+  background: linear-gradient(90deg, #cbd5e1 0%, transparent 100%);
+  margin-left: 16px;
 }
 }
 
 
-.advice-dialog >>> .el-dialog__title {
-  font-size: 16px;
-  font-weight: bold;
-  color: #1e293b;
+/* 卡片容器间距:确保上下卡片不粘连 */
+.card-col {
+  margin-bottom: 24px;
 }
 }
 
 
-.advice-content {
-  padding: 10px 5px;
-  line-height: 1.6;
-  color: #475569;
-  font-size: 14px;
+/* 2. 卡片内部样式优化 */
+.nice-card {
+  height: 300px;
+  border-radius: 12px;
+  border: 1px solid #e2e8f0;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  display: flex;
+  flex-direction: column;
 }
 }
 
 
-.markdown-body {
-  background: #f8fafc;
-  padding: 15px;
-  border-radius: 8px;
-  border-left: 4px solid #409eff;
+.nice-card:hover {
+  transform: translateY(-4px);
 }
 }
 
 
-/* 之前已有的样式保持不变 */
-.monitor-report-container { padding: 24px; background-color: #f6f8fb; min-height: 100vh; }
-.force-row { display: flex !important; flex-wrap: wrap !important; }
-.nice-card { height: 320px; border-radius: 12px; display: flex; flex-direction: column; margin-bottom: 20px; overflow: hidden; }
-.nice-card >>> .el-card__header { padding: 15px 20px; background-color: #fafbfd; border-bottom: 1px solid #f1f5f9; flex-shrink: 0; }
-.nice-card >>> .el-card__body { padding: 0; flex: 1; display: flex; flex-direction: column; overflow: hidden; }
-.card-header { display: flex; justify-content: space-between; align-items: center; }
-.title-wrapper { display: flex; align-items: center; flex: 1; }
-.status-tag { border-radius: 10px; padding: 0 8px; font-weight: bold; }
-.icon-accent { font-size: 18px; margin-right: 10px; }
-.title-text { font-size: 14px; font-weight: 600; color: #334155; }
-.title-text small { font-weight: normal; font-size: 11px; color: #94a3b8; display: block; }
-.card-body { padding: 15px 20px; flex: 1; overflow-y: auto; }
-.progress-item { margin-bottom: 18px; }
-.progress-item .info { display: flex; justify-content: space-between; margin-bottom: 6px; font-size: 12px; }
-.status-placeholder { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #cbd5e1; }
-.warning-shadow { border-top: 3px solid #e6a23c; }
-.danger-shadow { border-top: 3px solid #f56c6c; }
-.primary-shadow { border-top: 3px solid #409eff; }
-.warning-text { color: #e6a23c; }
-.danger-text { color: #f56c6c; }
-.primary-text { color: #409eff; }
-.text-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 180px; }
-.scroll-style::-webkit-scrollbar { width: 4px; }
-.scroll-style::-webkit-scrollbar-thumb { background: #e2e8f0; border-radius: 10px; }
+.nice-card >>> .el-card__header {
+  padding: 16px 20px;
+  background-color: #ffffff;
+  border-bottom: 1px solid #f1f5f9;
+}
 
 
-.status-banner {
+.card-header {
   display: flex;
   display: flex;
   justify-content: space-between;
   justify-content: space-between;
   align-items: center;
   align-items: center;
-  padding: 24px 32px;
-  background: #ffffff;
-  border-radius: 16px;
-  margin-bottom: 24px;
-  position: relative;
-  overflow: hidden;
-  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.05);
-  border: 1px solid rgba(255, 255, 255, 0.8);
-}
-
-/* 状态颜色背景微调 */
-.banner-success {
-  background: linear-gradient(135deg, #f0fdf4 0%, #ffffff 50%);
-  border-left: 6px solid #22c55e;
-}
-.banner-warning {
-  background: linear-gradient(135deg, #fffbeb 0%, #ffffff 50%);
-  border-left: 6px solid #f59e0b;
 }
 }
 
 
-/* 左侧状态图标与动画 */
-.banner-left {
+.title-wrapper {
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
-  gap: 20px;
+  gap: 12px;
 }
 }
 
 
-.status-visual {
-  position: relative;
-  width: 48px;
-  height: 48px;
+.text-group {
+  display: flex;
+  flex-direction: column;
 }
 }
 
 
-.status-icon-wrapper {
-  width: 48px;
-  height: 48px;
-  border-radius: 14px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  font-size: 24px;
-  z-index: 2;
-  position: relative;
+.main-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #334155;
 }
 }
 
 
-.banner-success .status-icon-wrapper { background: #dcfce7; color: #16a34a; }
-.banner-warning .status-icon-wrapper { background: #fef3c7; color: #d97706; }
+.sub-title {
+  font-size: 11px;
+  color: #94a3b8;
+  margin-top: 2px;
+}
 
 
-.pulse-ring {
-  position: absolute;
-  top: 0; left: 0; width: 100%; height: 100%;
-  border-radius: 14px;
-  animation: pulse 2s infinite;
+.card-body {
+  padding: 16px 20px;
+  flex: 1;
+  overflow-y: auto;
 }
 }
-.banner-success .pulse-ring { background: rgba(34, 197, 94, 0.2); }
-.banner-warning .pulse-ring { background: rgba(245, 158, 11, 0.2); }
 
 
-@keyframes pulse {
-  0% { transform: scale(1); opacity: 1; }
-  100% { transform: scale(1.5); opacity: 0; }
+/* 指标条目 */
+.metric-entry {
+  margin-bottom: 16px;
 }
 }
 
 
-/* 内容文本区 */
-.banner-title {
-  font-size: 20px;
-  font-weight: 800;
-  color: #1e293b;
+.entry-info {
   display: flex;
   display: flex;
-  align-items: center;
-  gap: 12px;
-  margin-bottom: 4px;
+  justify-content: space-between;
+  margin-bottom: 6px;
+  font-size: 12px;
 }
 }
 
 
-.banner-desc {
-  font-size: 14px;
+.entry-name {
   color: #64748b;
   color: #64748b;
-  display: flex;
-  flex-direction: column;
-  gap: 4px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  max-width: 160px;
 }
 }
 
 
-.update-time {
-  font-size: 12px;
-  color: #94a3b8;
+.entry-val {
+  font-weight: 700;
 }
 }
+.entry-val small { font-weight: 400; font-size: 10px; margin-left: 2px; }
+
+/* 3. 顶部 Banner 样式 (沿用并微调) */
+.status-banner {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 24px 32px;
+  background: #ffffff;
+  border-radius: 16px;
+  margin-bottom: 32px;
+  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05);
+  border: 1px solid #e2e8f0;
+}
+
+.banner-success { border-left: 6px solid #22c55e; }
+.banner-warning { border-left: 6px solid #f59e0b; }
 
 
-/* 中间指标区 */
 .banner-stats {
 .banner-stats {
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
-  background: rgba(248, 250, 252, 0.8);
+  background: #f1f5f9;
   padding: 12px 24px;
   padding: 12px 24px;
   border-radius: 12px;
   border-radius: 12px;
   gap: 20px;
   gap: 20px;
 }
 }
 
 
-.stat-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-}
+.stat-item { text-align: center; }
+.stat-label { font-size: 11px; color: #94a3b8; text-transform: uppercase; }
+.stat-value { font-size: 18px; font-weight: 800; color: #1e293b; }
+.stat-divider { width: 1px; height: 24px; background: #cbd5e1; }
 
 
-.stat-label {
-  font-size: 11px;
-  text-transform: uppercase;
-  letter-spacing: 0.5px;
-  color: #94a3b8;
-  margin-bottom: 2px;
+/* 颜色状态与阴影 */
+.warning-shadow {
+  border-top: 4px solid #e6a23c !important;
+  box-shadow: 0 8px 20px -6px rgba(230, 162, 60, 0.2) !important;
 }
 }
-
-.stat-value {
-  font-size: 18px;
-  font-weight: 700;
-  color: #334155;
+.danger-shadow {
+  border-top: 4px solid #f56c6c !important;
+  box-shadow: 0 8px 20px -6px rgba(245, 108, 108, 0.2) !important;
 }
 }
-.stat-value small { font-size: 12px; font-weight: normal; }
-.risk-text { color: #ef4444; }
-
-.stat-divider {
-  width: 1px;
-  height: 24px;
-  background: #e2e8f0;
+.primary-shadow {
+  border-top: 4px solid #409eff !important;
+  box-shadow: 0 8px 20px -6px rgba(64, 158, 255, 0.2) !important;
 }
 }
 
 
-/* 按钮发光效果 */
-.refresh-glow {
-  box-shadow: 0 4px 14px 0 rgba(64, 158, 255, 0.3);
-  transition: all 0.3s ease;
+.warning-text { color: #d97706; }
+.danger-text { color: #dc2626; }
+.primary-text { color: #2563eb; }
+
+/* 其他辅助类 */
+.empty-state {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  color: #cbd5e1;
 }
 }
+.empty-state i { font-size: 28px; margin-bottom: 8px; }
+
+.scroll-style::-webkit-scrollbar { width: 4px; }
+.scroll-style::-webkit-scrollbar-thumb { background: #e2e8f0; border-radius: 10px; }
+
 .refresh-glow:hover {
 .refresh-glow:hover {
-  transform: translateY(-1px);
-  box-shadow: 0 6px 20px rgba(64, 158, 255, 0.5);
+  box-shadow: 0 0 15px rgba(64, 158, 255, 0.4);
 }
 }
 </style>
 </style>