Browse Source

添加一个每日巡检页面 DailyReport.vue

reghao 4 weeks ago
parent
commit
61f4297f90
4 changed files with 565 additions and 794 deletions
  1. 374 0
      src/views/admin/DailyReport.vue
  2. 191 360
      src/views/admin/Dashboard.vue
  3. 0 176
      src/views/admin/Dashboard1.vue
  4. 0 258
      src/views/admin/Dashboard2.vue

+ 374 - 0
src/views/admin/DailyReport.vue

@@ -0,0 +1,374 @@
+<template>
+  <div v-loading="loading" class="monitor-report-container">
+    <div class="status-banner" :class="'banner-' + overallStatus">
+      <div class="banner-left">
+        <div class="status-visual">
+          <div class="status-icon-wrapper">
+            <i :class="overallStatus === 'success' ? 'el-icon-circle-check' : 'el-icon-warning-outline'" />
+          </div>
+          <div class="pulse-ring" />
+        </div>
+        <div class="banner-content">
+          <div class="banner-title">
+            {{ reportData.statusSummary || '核心基础设施巡检' }}
+            <el-tag size="mini" :type="overallStatus === 'success' ? 'success' : 'warning'" effect="dark">
+              {{ overallStatus === 'success' ? 'HEALTHY' : 'ACTION REQUIRED' }}
+            </el-tag>
+          </div>
+          <div class="banner-desc">
+            <span>{{ overallDescription }}</span>
+            <span class="update-time"><i class="el-icon-time" /> 更新时间: {{ reportData.lastUpdateTime || '-' }}</span>
+          </div>
+        </div>
+      </div>
+
+      <div class="banner-stats">
+        <div class="stat-item">
+          <div class="stat-label">巡检项</div>
+          <div class="stat-value">{{ totalTaskCount }}</div>
+        </div>
+        <div class="stat-divider" />
+        <div class="stat-item">
+          <div class="stat-label">风险项</div>
+          <div class="stat-value" :class="hasAnyIssue ? 'risk-text' : ''">{{ totalIssuesCount }}</div>
+        </div>
+      </div>
+
+      <div class="banner-right">
+        <el-button type="primary" size="small" icon="el-icon-refresh" :loading="loading" class="refresh-btn" @click="loadReport">
+          同步实时数据
+        </el-button>
+      </div>
+    </div>
+
+    <div v-for="(taskKeys, catName) in reportData.categoryMap" :key="catName" class="category-group">
+      <div class="category-header">
+        <div class="category-title-box">
+          <span class="category-name">{{ catName }}</span>
+          <el-badge
+            :value="getCategoryIssueCount(taskKeys)"
+            :hidden="getCategoryIssueCount(taskKeys) === 0"
+            type="danger"
+            class="cat-badge"
+          />
+        </div>
+        <div class="category-line" />
+      </div>
+
+      <el-row :gutter="20" type="flex" style="flex-wrap: wrap;">
+        <el-col
+          v-for="key in taskKeys"
+          :key="key"
+          :xs="24"
+          :sm="12"
+          :md="8"
+          :lg="6"
+          class="card-col"
+        >
+          <el-card v-if="reportData.resultMap[key]" shadow="hover" class="nice-card" :class="getShadowClass(reportData.resultMap[key])">
+            <div slot="header" class="card-header">
+              <div class="title-wrapper">
+                <i :class="[reportData.resultMap[key].icon, 'icon-accent', reportData.resultMap[key].textColor]" />
+                <div class="text-group">
+                  <span class="main-title" :title="reportData.resultMap[key].title">{{ reportData.resultMap[key].title }}</span>
+                  <span class="sub-title">{{ reportData.resultMap[key].subtitle }}</span>
+                </div>
+              </div>
+              <div class="header-ops">
+                <el-tooltip content="排查指引" placement="top">
+                  <i class="el-icon-question advice-icon" @click="showAdvice(reportData.resultMap[key])" />
+                </el-tooltip>
+                <el-tag v-if="reportData.resultMap[key].metricItemList.length > 0" :type="reportData.resultMap[key].tagType" size="mini" effect="dark">
+                  {{ reportData.resultMap[key].metricItemList.length }}
+                </el-tag>
+              </div>
+            </div>
+
+            <div class="card-body-wrapper scroll-style">
+              <template v-if="reportData.resultMap[key].metricItemList && reportData.resultMap[key].metricItemList.length > 0">
+                <div v-for="(item, index) in reportData.resultMap[key].metricItemList" :key="index" class="metric-entry">
+                  <div class="entry-info">
+                    <span class="entry-name" :title="item.name || item.instance">
+                      {{ item.name || item.instance }}
+                      <el-tag size="mini" effect="plain" class="value-type-tag">
+                        {{ getValueTypeLabel(reportData.resultMap[key].valueType) }}
+                      </el-tag>
+                    </span>
+                    <span class="entry-val" :class="reportData.resultMap[key].textColor">
+                      {{ item.value }}<small>{{ reportData.resultMap[key].unit }}</small>
+                    </span>
+                  </div>
+                  <el-progress
+                    :percentage="calculatePercentage(item.value, reportData.resultMap[key])"
+                    :show-text="false"
+                    :stroke-width="6"
+                    :color="reportData.resultMap[key].progColor"
+                  />
+                </div>
+              </template>
+              <div v-else class="empty-state">
+                <i class="el-icon-circle-check" :style="{color: '#67C23A'}" />
+                <p>{{ reportData.resultMap[key].emptyText || '状态健康' }}</p>
+              </div>
+            </div>
+          </el-card>
+        </el-col>
+      </el-row>
+    </div>
+
+    <el-dialog :title="'💡 排查建议: ' + currentAdvice.title" :visible.sync="dialogVisible" width="500px" append-to-body>
+      <div class="advice-content" v-html="formatAdvice(currentAdvice.text)" />
+      <div slot="footer"><el-button type="primary" size="small" @click="dialogVisible = false">确 定</el-button></div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getDashboard } from '@/api/devops'
+
+export default {
+  data() {
+    return {
+      loading: false,
+      dialogVisible: false,
+      currentAdvice: { title: '', text: '' },
+      reportData: {
+        categoryMap: {},
+        resultMap: {},
+        statusSummary: '',
+        lastUpdateTime: ''
+      }
+    }
+  },
+  computed: {
+    totalTaskCount() {
+      return Object.keys(this.reportData.resultMap || {}).length
+    },
+    totalIssuesCount() {
+      return Object.values(this.reportData.resultMap || {})
+        .filter(item => item.metricItemList && item.metricItemList.length > 0).length
+    },
+    hasAnyIssue() { return this.totalIssuesCount > 0 },
+    overallStatus() { return this.hasAnyIssue ? 'warning' : 'success' },
+    overallDescription() {
+      return this.hasAnyIssue ? `检测到系统风险项,请按指引及时修复。` : '核心基础设施所有参数均在安全范围内。'
+    }
+  },
+  created() { this.loadReport() },
+  methods: {
+    getCategoryIssueCount(taskKeys) {
+      return taskKeys.filter(key => {
+        const res = this.reportData.resultMap[key]
+        return res && res.metricItemList && res.metricItemList.length > 0
+      }).length
+    },
+    getShadowClass(config) {
+      return config.metricItemList.length > 0 ? config.tagType + '-shadow' : ''
+    },
+    calculatePercentage(val, config) {
+      if (!config.ratio) return 100
+      return val > 100 ? 100 : (val < 0 ? 0 : val)
+    },
+    async loadReport() {
+      this.loading = true
+      try {
+        const resp = await getDashboard()
+        if (resp && resp.code === 0) {
+          this.reportData = resp.data
+        }
+      } catch (e) {
+        this.$message.error('获取巡检报告失败')
+      } finally {
+        this.loading = false
+      }
+    },
+    showAdvice(config) {
+      const opMap = { 'gt': '>', 'lt': '<', 'eq': '=', 'ne': '!=' }
+      const opStr = opMap[config.operator] || '>'
+
+      this.currentAdvice = {
+        title: config.title,
+        // 在建议文本前加上判定标准,让运维知道为什么报警
+        text: `<b>判定标准:</b> ${this.getValueTypeLabel(config.valueType)}值 ${opStr} ${config.threshold}${config.unit}<br><br>` +
+          (config.advice || '暂无详细指引。')
+      }
+      this.dialogVisible = true
+    },
+    formatAdvice(text) {
+      return text ? text.replace(/\n/g, '<br>') : ''
+    },
+    getValueTypeLabel(type) {
+      const map = {
+        'INSTANT': '实时',
+        'AVG': '24H平均',
+        'MAX': '24H峰值',
+        'MIN': '24H最低',
+        'INCREASE': '24H累计'
+      }
+      return map[type] || '数值'
+    }
+  }
+}
+</script>
+
+<style scoped>
+/* 容器 */
+.monitor-report-container {
+  padding: 24px;
+  background-color: #f5f7fa;
+  min-height: 100vh;
+}
+
+/* Banner 样式 */
+.status-banner {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px 24px;
+  background: #fff;
+  border-radius: 8px;
+  margin-bottom: 24px;
+  box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05);
+  border-left: 6px solid #dcdfe6;
+}
+.banner-success { border-left-color: #67C23A; }
+.banner-warning { border-left-color: #E6A23C; }
+
+.banner-left { display: flex; align-items: center; gap: 16px; }
+.status-icon-wrapper { font-size: 28px; }
+.banner-success .status-icon-wrapper { color: #67C23A; }
+.banner-warning .status-icon-wrapper { color: #E6A23C; }
+
+.banner-title { font-size: 16px; font-weight: bold; color: #303133; display: flex; align-items: center; gap: 8px; }
+.banner-desc { font-size: 13px; color: #909399; margin-top: 4px; }
+.update-time { margin-left: 12px; font-weight: normal; }
+
+.banner-stats { display: flex; align-items: center; gap: 24px; background: #f8f9fb; padding: 8px 20px; border-radius: 6px; }
+.stat-item { text-align: center; }
+.stat-label { font-size: 12px; color: #909399; margin-bottom: 2px; }
+.stat-value { font-size: 18px; font-weight: bold; color: #303133; }
+.stat-divider { width: 1px; height: 24px; background: #ebeef5; }
+
+/* 分类标题 */
+.category-group { margin-bottom: 32px; }
+.category-header { display: flex; align-items: center; margin-bottom: 16px; }
+.category-title-box { display: flex; align-items: center; background: #303133; padding: 5px 12px; border-radius: 4px; }
+.category-name { color: #fff; font-size: 13px; font-weight: bold; letter-spacing: 1px; }
+.category-line { flex: 1; height: 1px; background: linear-gradient(90deg, #dcdfe6 0%, transparent 100%); margin-left: 16px; }
+
+/* 卡片布局修复:解决溢出的核心 */
+.card-col {
+  margin-bottom: 24px;
+  /* 强制清除可能存在的浮动错位 */
+  display: inline-block;
+  float: none !important;
+  vertical-align: top;
+}
+.nice-card {
+  height: 260px !important; /* 必须固定高度 */
+  max-height: 260px !important;
+  display: flex !important;
+  flex-direction: column !important;
+  overflow: hidden !important;
+  box-sizing: border-box;
+}
+
+/* ElementUI el-card 内部结构穿透修改 */
+.nice-card >>> .el-card__header {
+  padding: 12px 16px;
+  background-color: #fafafa;
+  flex-shrink: 0; /* 头部固定,不参与压缩 */
+  border-bottom: 1px solid #ebeef5;
+}
+
+.nice-card >>> .el-card__body {
+  padding: 0 !important;
+  flex: 1 !important;
+  display: flex !important;
+  flex-direction: column !important;
+  overflow: hidden !important; /* 禁止外层滚动 */
+  height: calc(260px - 55px); /* 减去 header 大约高度,确保主体高度固定 */
+}
+
+/* 核心滚动区域 */
+.card-body-wrapper {
+  flex: 1 !important;
+  overflow-y: auto !important; /* 只有这里允许纵向滚动 */
+  padding: 16px;
+  box-sizing: border-box;
+}
+
+/* 头部内容布局 */
+.card-header { display: flex; justify-content: space-between; align-items: center; }
+.title-wrapper { display: flex; align-items: center; gap: 8px; overflow: hidden; }
+.text-group { display: flex; flex-direction: column; overflow: hidden; }
+.main-title {
+  font-size: 13px; font-weight: bold; color: #303133;
+  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+}
+.sub-title { font-size: 11px; color: #909399; margin-top: 2px; }
+.header-ops { display: flex; align-items: center; gap: 6px; }
+.advice-icon { color: #909399; cursor: pointer; transition: color 0.2s; }
+.advice-icon:hover { color: #409eff; }
+
+/* 指标项样式 */
+.metric-entry { margin-bottom: 14px; }
+.metric-entry:last-child { margin-bottom: 0; }
+.entry-info { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 6px; }
+.value-type-tag {
+  margin-left: 6px;
+  padding: 0 4px;
+  height: 18px;
+  line-height: 16px;
+  font-size: 10px;
+  border-radius: 2px;
+  opacity: 0.8;
+  vertical-align: middle;
+}
+.entry-name {
+  font-size: 12px;
+  color: #606266;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 75%; /* 稍微加宽一点点 */
+  display: flex;
+  align-items: center;
+}
+.entry-val { font-size: 13px; font-weight: bold; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; }
+.entry-val small { font-size: 10px; margin-left: 2px; font-weight: normal; }
+
+/* 异常状态装饰 */
+.warning-shadow { border-top: 3px solid #E6A23C !important; }
+.danger-shadow { border-top: 3px solid #F56C6C !important; }
+.primary-shadow { border-top: 3px solid #409EFF !important; }
+
+/* 文本颜色 */
+.warning-text { color: #E6A23C; }
+.danger-text { color: #F56C6C; }
+.primary-text { color: #409EFF; }
+
+/* 空状态 */
+.empty-state {
+  height: 100%;
+  min-height: 150px; /* 保证空状态也有足够高度占位 */
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+.empty-state i { font-size: 32px; margin-bottom: 12px; opacity: 0.6; }
+.empty-state p { font-size: 12px; }
+
+/* 滚动条美化 */
+.scroll-style::-webkit-scrollbar { width: 5px; }
+.scroll-style::-webkit-scrollbar-thumb { background: #e4e7ed; border-radius: 10px; }
+.scroll-style::-webkit-scrollbar-thumb:hover { background: #dcdfe6; }
+
+/* 弹窗指引内容 */
+.advice-content {
+  line-height: 1.6; color: #606266; font-size: 14px;
+  background: #f8f9fb; padding: 16px; border-radius: 4px;
+  border-left: 4px solid #409eff;
+}
+</style>

+ 191 - 360
src/views/admin/Dashboard.vue

@@ -1,437 +1,268 @@
 <template>
-  <div class="monitor-report-container" v-loading="loading">
-    <div class="status-banner" :class="'banner-' + overallStatus">
-      <div class="banner-left">
-        <div class="status-visual">
-          <div class="status-icon-wrapper">
-            <i :class="overallStatus === 'success' ? 'el-icon-circle-check' : 'el-icon-warning-outline'"></i>
-          </div>
-          <div class="pulse-ring"></div>
-        </div>
-        <div class="banner-content">
-          <div class="banner-title">
-            {{ reportData.statusSummary || '核心基础设施巡检' }}
-            <el-tag size="mini" :type="overallStatus === 'success' ? 'success' : 'warning'" effect="plain">
-              {{ overallStatus === 'success' ? 'HEALTHY' : 'ACTION REQUIRED' }}
+  <div class="dashboard-wrapper">
+    <el-row :gutter="20" class="stat-row">
+      <el-col :span="24">
+        <el-card shadow="never" class="role-card">
+          <div class="role-container">
+            <span class="role-label"><i class="el-icon-medal"></i> 当前权限角色:</span>
+            <el-tag
+              v-for="(item, index) in roles"
+              :key="index"
+              effect="dark"
+              size="medium"
+              class="role-tag"
+              @click="goToRole(item)"
+            >
+              {{ item }}
             </el-tag>
           </div>
-          <div class="banner-desc">
-            <span>{{ overallDescription }}</span>
-            <span class="update-time"><i class="el-icon-time"></i> 最后更新: {{ reportData.lastUpdateTime || '-' }}</span>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <el-row :gutter="20" class="process-row">
+      <el-col :span="24">
+        <el-card shadow="hover">
+          <div slot="header" class="card-header">
+            <span><i class="el-icon-refresh"></i> CI/CD 流水线</span>
           </div>
-        </div>
-      </div>
-
-      <div class="banner-stats">
-        <div class="stat-item">
-          <div class="stat-label">巡检项</div>
-          <div class="stat-value">{{ cardConfigs.length }}</div>
-        </div>
-        <div class="stat-divider"></div>
-        <div class="stat-item">
-          <div class="stat-label">风险项</div>
-          <div class="stat-value" :class="hasAnyIssue ? 'risk-text' : ''">{{ totalIssuesCount }}</div>
-        </div>
-      </div>
-
-      <div class="banner-right">
-        <el-button
-          type="primary"
-          size="medium"
-          icon="el-icon-refresh"
-          @click="loadReport"
-          :loading="loading"
-          class="refresh-glow"
-        >
-          重新巡检
-        </el-button>
-      </div>
-    </div>
-
-    <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-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)">
-            <div slot="header" class="card-header">
-              <div class="title-wrapper">
-                <i :class="[config.icon, 'icon-accent', config.textColor]"></i>
-                <div class="text-group">
-                  <span class="main-title">{{ config.title }}</span>
-                  <span class="sub-title">{{ config.subTitle }}</span>
-                </div>
-              </div>
-              <div class="header-ops">
-                <el-tooltip content="排查建议" placement="top">
-                  <el-button
-                    type="text"
-                    icon="el-icon-question"
-                    class="advice-btn"
-                    @click="showAdvice(config)"
-                  ></el-button>
-                </el-tooltip>
-                <el-tag v-if="getData(config.key).length > 0" :type="config.tagType" size="mini" effect="dark">
-                  {{ getData(config.key).length }}
-                </el-tag>
-              </div>
-            </div>
-
-            <div class="card-body scroll-style">
-              <template v-if="getData(config.key).length > 0">
-                <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>
-                  <el-progress
-                    :percentage="config.isRatio ? (item.value > 100 ? 100 : item.value) : 100"
-                    :show-text="false"
-                    :color="config.progColor"
-                    :stroke-width="6"
-                  />
+          <div class="steps-wrapper">
+            <el-steps :active="1" finish-status="success" align-center>
+              <el-step title="更新代码" icon="el-icon-edit-outline" />
+              <el-step title="编译代码" icon="el-icon-set-up" />
+              <el-step title="应用打包" icon="el-icon-box" />
+              <el-step title="推送应用" icon="el-icon-upload" />
+              <el-step title="拉取应用" icon="el-icon-download" />
+              <el-step title="部署应用" icon="el-icon-monitor" />
+            </el-steps>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <el-row :gutter="20">
+      <el-col :md="12" :sm="24">
+        <el-card shadow="hover" class="data-card">
+          <div slot="header" class="card-header">
+            <span><i class="el-icon-cpu"></i> 机器节点状态</span>
+          </div>
+          <el-table :data="machineStatList" border stripe size="small">
+            <el-table-column prop="env" label="运行环境">
+              <template slot-scope="scope">
+                <el-tag size="mini" effect="plain">{{ scope.row.env }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column prop="total" label="资产总数" align="center" />
+            <el-table-column label="健康状况" align="center">
+              <template slot-scope="scope">
+                <div class="status-cell">
+                  <span class="status-dot online"></span>
+                  <span class="status-count">{{ scope.row.onlineCount }}</span>
+                  <span class="status-divider">/</span>
+                  <span class="status-dot offline"></span>
+                  <span class="status-count">{{ scope.row.offlineCount }}</span>
                 </div>
               </template>
-              <div v-else class="empty-state">
-                <i class="el-icon-circle-check"></i>
-                <p>{{ config.emptyText }}</p>
-              </div>
-            </div>
-          </el-card>
-        </el-col>
-      </el-row>
-    </div>
-
-    <el-dialog
-      :title="'💡 排查建议: ' + currentAdvice.title"
-      :visible.sync="dialogVisible"
-      width="550px"
-      custom-class="advice-dialog"
-      append-to-body
-    >
-      <div class="advice-container">
-        <div v-if="currentAdvice.text" class="advice-box">
-          <p v-html="formatAdvice(currentAdvice.text)"></p>
-        </div>
-        <el-empty v-else description="暂无指引" :image-size="60"></el-empty>
-      </div>
-      <span slot="footer">
-        <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
-      </span>
-    </el-dialog>
+            </el-table-column>
+          </el-table>
+        </el-card>
+      </el-col>
+
+      <el-col :md="12" :sm="24">
+        <el-card shadow="hover" class="data-card">
+          <div slot="header" class="card-header">
+            <span><i class="el-icon-info"></i> 系统核心信息</span>
+          </div>
+          <el-descriptions v-if="sysInfo !== null" :column="1" border size="small">
+            <el-descriptions-item label="应用版本">
+              <el-link
+                type="primary"
+                target="_blank"
+                :href="`https://git.reghao.cn/reghao/devops/commit/${sysInfo.commitId}`"
+                icon="el-icon-link"
+              >
+                {{ sysInfo.commitId.substring(0, 8) }}
+              </el-link>
+            </el-descriptions-item>
+            <el-descriptions-item label="机器地址">
+              <code>{{ sysInfo.ipv4 }}</code>
+            </el-descriptions-item>
+            <el-descriptions-item label="操作系统">
+              <i class="el-icon-monitor"></i> {{ sysInfo.osInfo }}
+            </el-descriptions-item>
+            <el-descriptions-item label="JVM 环境">
+              <el-tooltip effect="dark" :content="sysInfo.jvmInfo" placement="top">
+                <span class="text-truncate">{{ sysInfo.jvmInfo }}</span>
+              </el-tooltip>
+            </el-descriptions-item>
+            <el-descriptions-item label="启动/PID">
+              <el-tag size="mini" type="success">{{ sysInfo.startAt }}</el-tag>
+              <el-tag size="mini" type="warning" style="margin-left:5px">PID: {{ sysInfo.pid }}</el-tag>
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-card>
+      </el-col>
+    </el-row>
   </div>
 </template>
 
 <script>
 import { getDashboard } from '@/api/devops'
+import { getAuthedUser } from '@/utils/auth'
 
 export default {
+  name: 'Dashboard',
   data() {
     return {
-      loading: false,
-      dialogVisible: false,
-      currentAdvice: { title: '', text: '' },
-      // 业务分类
-      categories: ['计算资源', '内存指标', '存储文件', '网络协议', '基础环境'],
-      reportData: {
-        advices: {},
-        // 动态数据将通过接口覆盖
-      },
-      // 完整的 22 个指标配置
-      cardConfigs: [
-        /* 计算资源 */
-        { 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: '同步精确' }
-      ]
+      roles: [],
+      machineStatList: [],
+      sysInfo: null
     }
   },
-  computed: {
-    totalIssuesCount() {
-      return this.cardConfigs.filter(c => this.getData(c.key).length > 0).length
-    },
-    hasAnyIssue() { return this.totalIssuesCount > 0 },
-    overallStatus() { return this.hasAnyIssue ? 'warning' : 'success' },
-    overallDescription() {
-      return this.hasAnyIssue ? `当前环境存在异常项,请优先处理风险卡片。` : '核心基础设施所有参数均在安全范围内。'
-    }
+  created() {
+    const { roles } = getAuthedUser()
+    this.roles = roles
+    document.title = 'Dashboard'
+    this.getData()
   },
-  created() { this.loadReport() },
   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() {
+      this.getDevopsDashboard()
     },
-    getData(key) { return this.reportData[key] || [] },
-    getShadowClass(config) {
-      return this.getData(config.key).length > 0 ? config.tagType + '-shadow' : ''
-    },
-    async loadReport() {
-      this.loading = true
-      try {
-        const resp = await getDashboard()
+    getDevopsDashboard() {
+      getDashboard().then(resp => {
         if (resp.code === 0) {
-          this.reportData = Object.assign({}, this.reportData, resp.data)
+          this.sysInfo = resp.data.sysInfo
+          this.machineStatList = resp.data.machineStatList
+        } else {
+          this.$message.error(resp.msg)
         }
-      } finally { this.loading = false }
+      }).catch(error => {
+        this.$message.error(error.message)
+      })
     },
-    showAdvice(config) {
-      this.currentAdvice = {
-        title: config.title,
-        text: this.reportData.advices[config.key] || '暂无详细指引。'
-      }
-      this.dialogVisible = true
+    goToRole(data) {
+      this.$message.info('当前操作角色: ' + data)
     },
-    formatAdvice(text) { return text ? text.replace(/\n/g, '<br>') : '' }
+    // 其余逻辑保持不变...
+    goToDisk() { const path = '/disk'; this.handleRoute(path) },
+    goToVod() { const path = '/vod'; this.handleRoute(path) },
+    goToBlog() { const path = '/blog'; this.handleRoute(path) },
+    handleRoute(path) {
+      if (this.$route.path === path) { this.$router.go(0); return }
+      this.$router.push(path)
+    }
   }
 }
 </script>
 
 <style scoped>
-/* 1. 基础容器与间距控制 */
-.monitor-report-container {
-  padding: 32px;
-  background-color: #f8fafc;
-  min-height: 100vh;
+.dashboard-wrapper {
+  padding: 10px;
 }
 
-/* 分类组间距 */
-.category-group {
-  margin-bottom: 40px;
-  background: rgba(255, 255, 255, 0.4);
-  padding: 20px;
-  border-radius: 16px;
+.stat-row {
+  margin-bottom: 20px;
 }
 
-/* 分类头部标题 */
-.category-header {
-  display: flex;
-  align-items: center;
-  margin-bottom: 24px;
-  padding: 0 4px;
+.role-card {
+  background: #fcfcfc;
+  border-left: 4px solid #409EFF;
 }
 
-.category-title-box {
+.role-container {
   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);
 }
 
-.category-name {
-  color: #f8fafc;
-  font-weight: 700;
-  font-size: 15px;
-  letter-spacing: 0.5px;
+.role-label {
+  font-weight: bold;
+  color: #606266;
+  margin-right: 15px;
 }
 
-.category-line {
-  flex: 1;
-  height: 2px;
-  background: linear-gradient(90deg, #cbd5e1 0%, transparent 100%);
-  margin-left: 16px;
+.role-tag {
+  margin-right: 10px;
+  cursor: pointer;
+  transition: all 0.3s;
 }
 
-/* 卡片容器间距:确保上下卡片不粘连 */
-.card-col {
-  margin-bottom: 24px;
+.role-tag:hover {
+  opacity: 0.8;
+  transform: translateY(-1px);
 }
 
-/* 2. 卡片内部样式优化 */
-.nice-card {
-  height: 300px;
-  border-radius: 12px;
-  border: 1px solid #e2e8f0;
-  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+.card-header {
   display: flex;
-  flex-direction: column;
+  justify-content: space-between;
+  align-items: center;
+  font-weight: bold;
 }
 
-.nice-card:hover {
-  transform: translateY(-4px);
+.process-row {
+  margin-bottom: 20px;
 }
 
-.nice-card >>> .el-card__header {
-  padding: 16px 20px;
-  background-color: #ffffff;
-  border-bottom: 1px solid #f1f5f9;
+.steps-wrapper {
+  padding: 20px 0;
 }
 
-.card-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
+.data-card {
+  min-height: 380px;
 }
 
-.title-wrapper {
+/* 状态点样式 */
+.status-cell {
   display: flex;
   align-items: center;
-  gap: 12px;
-}
-
-.text-group {
-  display: flex;
-  flex-direction: column;
+  justify-content: center;
 }
 
-.main-title {
-  font-size: 14px;
-  font-weight: 600;
-  color: #334155;
+.status-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  margin: 0 5px;
 }
 
-.sub-title {
-  font-size: 11px;
-  color: #94a3b8;
-  margin-top: 2px;
+.online {
+  background-color: #67C23A;
+  box-shadow: 0 0 5px #67C23A;
 }
 
-.card-body {
-  padding: 16px 20px;
-  flex: 1;
-  overflow-y: auto;
+.offline {
+  background-color: #F56C6C;
+  box-shadow: 0 0 5px #F56C6C;
 }
 
-/* 指标条目 */
-.metric-entry {
-  margin-bottom: 16px;
+.status-count {
+  font-weight: bold;
 }
 
-.entry-info {
-  display: flex;
-  justify-content: space-between;
-  margin-bottom: 6px;
-  font-size: 12px;
+.status-divider {
+  margin: 0 8px;
+  color: #DCDFE6;
 }
 
-.entry-name {
-  color: #64748b;
+.text-truncate {
+  display: inline-block;
+  max-width: 250px;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
-  max-width: 160px;
+  vertical-align: middle;
 }
 
-.entry-val {
-  font-weight: 700;
+code {
+  background-color: #f5f7fa;
+  padding: 2px 4px;
+  border-radius: 4px;
+  color: #e6a23c;
+  font-family: monospace;
 }
-.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 {
-  display: flex;
-  align-items: center;
-  background: #f1f5f9;
-  padding: 12px 24px;
-  border-radius: 12px;
-  gap: 20px;
-}
-
-.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; }
-
-/* 颜色状态与阴影 */
-.warning-shadow {
-  border-top: 4px solid #e6a23c !important;
-  box-shadow: 0 8px 20px -6px rgba(230, 162, 60, 0.2) !important;
-}
-.danger-shadow {
-  border-top: 4px solid #f56c6c !important;
-  box-shadow: 0 8px 20px -6px rgba(245, 108, 108, 0.2) !important;
-}
-.primary-shadow {
-  border-top: 4px solid #409eff !important;
-  box-shadow: 0 8px 20px -6px rgba(64, 158, 255, 0.2) !important;
-}
-
-.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 {
-  box-shadow: 0 0 15px rgba(64, 158, 255, 0.4);
+::v-deep .el-descriptions-item__label {
+  width: 120px;
+  background-color: #fafafa !important;
 }
 </style>

+ 0 - 176
src/views/admin/Dashboard1.vue

@@ -1,176 +0,0 @@
-<template>
-  <div class="monitor-report">
-    <el-card class="header-card" shadow="never">
-      <div slot="header" class="header-content">
-        <span class="title">🌐 基础设施资源监控日报</span>
-        <el-tag :type="reportData.status === '正常' ? 'success' : 'danger'" effect="dark">
-          状态:{{ reportData.statusSummary }}
-        </el-tag>
-      </div>
-      <div class="summary-text">
-        报告日期:{{ reportData.reportDate }} | 统计范围:全量生产集群节点
-      </div>
-    </el-card>
-
-    <el-row :gutter="20" class="chart-grid">
-      <el-col v-for="chart in chartConfigs" :key="chart.id" :span="12">
-        <el-card class="chart-card" shadow="hover">
-          <div slot="header" class="chart-header">
-            <i :class="chart.icon" /> {{ chart.title }}
-          </div>
-          <div :id="chart.id" class="chart-container" />
-        </el-card>
-      </el-col>
-    </el-row>
-  </div>
-</template>
-
-<script>
-import * as echarts from 'echarts'
-import {getDashboard} from "@/api/devops";
-
-export default {
-  name: 'Dashboard',
-  props: {
-    // 模拟从后端/父组件传入的数据
-    reportData: {
-      type: Object,
-      default: () => ({
-        reportDate: '2023-10-27',
-        statusSummary: '正常',
-        timeLabels: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '23:59'],
-        cpuSeries: { '192.168.1.1': [12, 15, 45, 30, 22, 18, 10], '192.168.1.2': [20, 22, 33, 55, 40, 30, 25] },
-        memSeries: { '192.168.1.1': [60, 62, 65, 70, 68, 65, 63], '192.168.1.2': [40, 42, 45, 48, 50, 52, 50] },
-        diskSeries: { '192.168.1.1': [5, 8, 12, 10, 8, 7, 5], '192.168.1.2': [15, 20, 25, 22, 20, 18, 15] },
-        netSeries: { '192.168.1.1': [2.5, 3.1, 8.5, 12.2, 7.4, 4.2, 3.0] }
-      })
-    }
-  },
-  data() {
-    return {
-      charts: {}, // 存储 echarts 实例
-      chartConfigs: [
-        { id: 'cpuChart', title: '计算 (CPU Usage %)', icon: 'el-icon-cpu', unit: '%', dataKey: 'cpuSeries', warn: 70, crit: 85 },
-        { id: 'memChart', title: '存储 (Memory Usage %)', icon: 'el-icon-monitor', unit: '%', dataKey: 'memSeries', warn: 75, crit: 90 },
-        { id: 'diskChart', title: '磁盘 (Disk I/O Saturation %)', icon: 'el-icon-coin', unit: '%', dataKey: 'diskSeries', warn: 80, crit: 95 },
-        { id: 'netChart', title: '网络 (Network Ingress MB/s)', icon: 'el-icon-connection', unit: 'MB/s', dataKey: 'netSeries', warn: null }
-      ]
-    }
-  },
-  mounted() {
-    this.initAllCharts()
-    window.addEventListener('resize', this.handleResize)
-  },
-  beforeDestroy() {
-    window.removeEventListener('resize', this.handleResize)
-    // 销毁实例,防止内存溢出
-    Object.values(this.charts).forEach(chart => chart.dispose())
-  },
-  methods: {
-    getData() {
-      getDashboard().then(resp => {
-        if (resp.code === 0) {
-          this.reportData = resp.data.reportData
-          this.statusSummary = resp.data.statusSummary
-          this.timeLabels = resp.data.timeLabels
-          this.cpuSeries = resp.data.cpuSeries
-          this.memSeries = resp.data.memSeries
-          this.diskSeries = resp.data.diskSeries
-          this.netSeries = resp.data.netSeries
-
-          // this.sysInfo = resp.data.sysInfo
-          // this.machineStatList = resp.data.machineStatList
-        } else {
-          this.$message.error(resp.msg)
-        }
-      }).catch(error => {
-        this.$message.error(error.message)
-      })
-    },
-    getWatermark(warn, crit) {
-      if (!warn) return {}
-      return {
-        symbol: ['none', 'none'],
-        silent: true,
-        data: [
-          { yAxis: warn, label: { formatter: '告警 {value}%' }, lineStyle: { color: '#E6A23C', type: 'dashed' }},
-          { yAxis: crit, label: { formatter: '临界 {value}%' }, lineStyle: { color: '#F56C6C', type: 'solid' }}
-        ]
-      }
-    },
-    initAllCharts() {
-      this.chartConfigs.forEach(conf => {
-        const chartDom = document.getElementById(conf.id)
-        const myChart = echarts.init(chartDom)
-
-        const seriesData = Object.keys(this.reportData[conf.dataKey]).map((ip, index) => ({
-          name: ip,
-          type: 'line',
-          smooth: true,
-          showSymbol: false,
-          data: this.reportData[conf.dataKey][ip],
-          markLine: index === 0 ? this.getWatermark(conf.warn, conf.crit) : {}
-        }))
-
-        const option = {
-          tooltip: { trigger: 'axis', backgroundColor: 'rgba(255, 255, 255, 0.9)', borderWidth: 1 },
-          legend: { bottom: '5', type: 'scroll', icon: 'circle' },
-          grid: { top: '15%', left: '3%', right: '8%', bottom: '15%', containLabel: true },
-          xAxis: { type: 'category', boundaryGap: false, data: this.reportData.timeLabels },
-          yAxis: { type: 'value', axisLabel: { formatter: `{value}${conf.unit}` }},
-          series: seriesData
-        }
-
-        myChart.setOption(option)
-        this.charts[conf.id] = myChart
-      })
-      this.getData()
-    },
-    handleResize() {
-      Object.values(this.charts).forEach(chart => chart.resize())
-    }
-  }
-}
-</script>
-
-<style scoped>
-.monitor-report {
-  padding: 20px;
-  background-color: #f5f7fa;
-  min-height: 100vh;
-}
-.header-card {
-  margin-bottom: 20px;
-  border-radius: 8px;
-}
-.header-content {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-.title {
-  font-size: 20px;
-  font-weight: bold;
-  color: #303133;
-}
-.summary-text {
-  font-size: 14px;
-  color: #909399;
-  margin-top: 5px;
-}
-.chart-grid {
-  margin-top: 10px;
-}
-.chart-card {
-  margin-bottom: 20px;
-  border-radius: 8px;
-}
-.chart-header {
-  font-weight: bold;
-  color: #409EFF;
-}
-.chart-container {
-  height: 350px;
-  width: 100%;
-}
-</style>

+ 0 - 258
src/views/admin/Dashboard2.vue

@@ -1,258 +0,0 @@
-<template>
-  <div class="monitor-dashboard">
-    <el-card class="header-card" shadow="never">
-      <div class="header-content">
-        <el-row type="flex" justify="space-between" align="middle">
-          <el-col :span="12">
-            <h2 class="title">🌐 基础设施资源监控 (24H)</h2>
-            <div class="summary">
-              数据日期:<el-tag size="small" type="info">{{ reportDate || '加载中...' }}</el-tag>
-              | 状态:<el-tag size="small" :type="statusType" effect="dark">{{ statusSummary }}</el-tag>
-            </div>
-          </el-col>
-          <el-col :span="12" class="header-ops">
-            <el-select v-model="refreshInterval" size="mini" placeholder="自动刷新" style="width: 110px; margin-right: 10px;">
-              <el-option label="不自动刷新" :value="0" />
-              <el-option label="1分钟刷新" :value="60000" />
-              <el-option label="5分钟刷新" :value="300000" />
-            </el-select>
-            <el-button
-              type="primary"
-              size="mini"
-              icon="el-icon-refresh"
-              :loading="loading"
-              @click="fetchData"
-            >
-              手动刷新
-            </el-button>
-          </el-col>
-        </el-row>
-      </div>
-    </el-card>
-
-    <el-row :gutter="20" class="pillar-grid" v-loading="loading">
-      <el-col
-        v-for="chart in chartConfigs"
-        :key="chart.id"
-        :xs="24"
-        :sm="24"
-        :md="12"
-        :lg="12"
-      >
-        <el-card class="chart-card" shadow="hover">
-          <div slot="header" class="card-header">
-            <i :class="chart.icon" style="margin-right: 8px; color: #409EFF;"></i>
-            <span class="header-text">{{ chart.title }}</span>
-          </div>
-          <div :id="chart.id" class="chart-container" />
-        </el-card>
-      </el-col>
-    </el-row>
-  </div>
-</template>
-
-<script>
-import * as echarts from 'echarts'
-import { getDashboard } from '@/api/devops'
-
-export default {
-  name: 'InfrastructureDashboard',
-  data() {
-    return {
-      loading: false,
-      reportDate: '',
-      statusSummary: '数据加载中...',
-      statusType: 'info',
-      charts: {},
-      refreshInterval: 0, // 默认不自动刷新
-      timer: null, // 定时器实例
-      chartConfigs: [
-        { id: 'cpuChart', title: 'CPU 使用率', icon: 'el-icon-monitor', unit: '%', key: 'cpuSeries', warn: 70, crit: 80 },
-        { id: 'memChart', title: '内存使用率', icon: 'el-icon-set-up', unit: '%', key: 'memSeries', warn: 70, crit: 85 },
-        { id: 'diskChart', title: '磁盘 I/O 饱和度', icon: 'el-icon-receiving', unit: '%', key: 'diskSeries', warn: 80, crit: 90 },
-        { id: 'netChart', title: '网络吞吐量', icon: 'el-icon-connection', unit: 'MB/s', key: 'netSeries', warn: null, crit: null }
-      ]
-    }
-  },
-  watch: {
-    // 监听刷新频率变化
-    refreshInterval(newVal) {
-      this.clearTimer()
-      if (newVal > 0) {
-        this.startTimer()
-      }
-    }
-  },
-  mounted() {
-    this.fetchData()
-    window.addEventListener('resize', this.handleResize)
-  },
-  activated() {
-    this.handleResize()
-    // 切回页面时,如果配置了自动刷新,重新开启
-    if (this.refreshInterval > 0) {
-      this.startTimer()
-    }
-  },
-  deactivated() {
-    // 切出页面(标签切换)时停止请求
-    this.clearTimer()
-  },
-  beforeDestroy() {
-    window.removeEventListener('resize', this.handleResize)
-    this.clearTimer()
-    Object.values(this.charts).forEach(chart => {
-      chart && chart.dispose()
-    })
-  },
-  methods: {
-    async fetchData() {
-      this.loading = true
-      try {
-        const resp = await getDashboard()
-        if (resp.code === 0) {
-          const reportData = resp.data
-          this.reportDate = reportData.reportDate
-          this.statusSummary = '数据已加载'
-          this.statusType = 'success'
-
-          this.$nextTick(() => {
-            this.initAllCharts(reportData)
-          })
-        } else {
-          this.$message.error(resp.msg || '获取数据失败')
-          this.statusType = 'danger'
-        }
-      } catch (error) {
-        console.error('Monitor Fetch Error:', error)
-        this.$message.error('数据刷新异常')
-      } finally {
-        this.loading = false
-      }
-    },
-
-    initAllCharts(data) {
-      this.chartConfigs.forEach(conf => {
-        const chartDom = document.getElementById(conf.id)
-        if (!chartDom) return
-
-        let myChart = echarts.getInstanceByDom(chartDom)
-        if (!myChart) {
-          myChart = echarts.init(chartDom)
-          this.charts[conf.id] = myChart
-        }
-
-        const option = this.getOption(conf, data)
-        // setOption 的第二个参数设为 true,确保新数据完全覆盖旧数据(不合并)
-        myChart.setOption(option, true)
-      })
-      // 渲染后统一进行一次 resize 适配容器
-      this.handleResize()
-    },
-
-    getOption(conf, rawData) {
-      const seriesData = rawData[conf.key] || {}
-      const timeLabels = rawData.timeLabels || []
-
-      const series = Object.keys(seriesData).map((ip, index) => ({
-        name: ip,
-        type: 'line',
-        smooth: true,
-        showSymbol: false,
-        data: seriesData[ip],
-        lineStyle: { width: 2 },
-        markLine: (index === 0 && conf.warn) ? this.getWatermarkLines(conf.warn, conf.crit) : null
-      }))
-
-      return {
-        tooltip: { trigger: 'axis', confine: true },
-        legend: { bottom: 0, type: 'scroll' },
-        grid: { top: 40, left: '3%', right: '5%', bottom: '15%', containLabel: true },
-        xAxis: { type: 'category', boundaryGap: false, data: timeLabels },
-        yAxis: { type: 'value', axisLabel: { formatter: `{value}${conf.unit}` } },
-        series: series
-      }
-    },
-
-    getWatermarkLines(warn, crit) {
-      return {
-        symbol: ['none', 'none'],
-        silent: true,
-        data: [
-          { yAxis: warn, lineStyle: { color: '#fa8c16', type: 'dashed' }, label: { position: 'end', formatter: 'Warn' } },
-          { yAxis: crit, lineStyle: { color: '#ff4d4f', type: 'dashed' }, label: { position: 'end', formatter: 'Crit' } }
-        ]
-      }
-    },
-
-    startTimer() {
-      this.timer = setInterval(() => {
-        // 如果当前正在加载,则跳过本次定时,避免请求堆积
-        if (!this.loading) {
-          this.fetchData()
-        }
-      }, this.refreshInterval)
-    },
-
-    clearTimer() {
-      if (this.timer) {
-        clearInterval(this.timer)
-        this.timer = null
-      }
-    },
-
-    handleResize() {
-      this.$nextTick(() => {
-        Object.values(this.charts).forEach(chart => {
-          chart && chart.resize()
-        })
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.monitor-dashboard {
-  padding: 15px;
-  background-color: #f0f2f5;
-  min-height: 100%;
-}
-
-.header-card {
-  margin-bottom: 15px;
-}
-
-.header-ops {
-  display: flex;
-  justify-content: flex-end;
-  align-items: center;
-}
-
-.title {
-  margin: 0 0 5px 0;
-  font-size: 18px;
-  color: #303133;
-}
-
-.summary {
-  color: #606266;
-  font-size: 12px;
-}
-
-.chart-card {
-  margin-bottom: 20px;
-  overflow: hidden;
-}
-
-.card-header {
-  border-left: 4px solid #409eff;
-  padding-left: 10px;
-  font-weight: bold;
-}
-
-.chart-container {
-  width: 100%;
-  height: 320px;
-}
-</style>