Просмотр исходного кода

refactor(frontend): LangGraphAdmin 重构为标签页布局 + 真实请求历史面板

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
E2E Test Bot 2 месяцев назад
Родитель
Сommit
912402f588
1 измененных файлов с 286 добавлено и 243 удалено
  1. 286 243
      cfc-web/src/views/admin/LangGraphAdmin.vue

+ 286 - 243
cfc-web/src/views/admin/LangGraphAdmin.vue

@@ -1,137 +1,179 @@
 <template>
-  <div class="langgraph-admin page-container">
-    <!-- 顶部状态栏 -->
-    <el-row :gutter="16" class="status-row">
-      <el-col :span="6">
-        <el-card :class="['status-card', statusClass]" shadow="hover">
-          <div class="status-icon">{{ statusIcon }}</div>
-          <div class="status-info">
-            <div class="status-label">服务状态</div>
-            <div class="status-value">{{ statusText }}</div>
-            <div class="status-sub">{{ statusTime }}</div>
-          </div>
+  <div class="lg-admin page-container">
+    <!-- 顶部状态栏(固定) -->
+    <el-row :gutter="12" class="top-bar">
+      <el-col :span="4">
+        <el-card shadow="hover" class="mini-card" :class="statusClass">
+          <div class="mini-icon">{{ statusIcon }}</div>
+          <div class="mini-text">{{ statusText }}</div>
+          <div class="mini-sub">{{ statusTime }}</div>
         </el-card>
       </el-col>
-      <el-col :span="6">
-        <el-card shadow="hover">
-          <div class="status-icon" style="background:linear-gradient(135deg,#667eea,#764ba2)">🔗</div>
-          <div class="status-info">
-            <div class="status-label">服务地址</div>
-            <div class="status-value" style="font-size:13px">{{ langgraphUrl }}</div>
-            <div class="status-sub">端口 9000 · v{{ serviceVersion }}</div>
-          </div>
+      <el-col :span="4">
+        <el-card shadow="hover" class="mini-card">
+          <div class="mini-icon" style="background:linear-gradient(135deg,#667eea,#764ba2)">🔗</div>
+          <div class="mini-text">{{ langgraphUrl }}</div>
+          <div class="mini-sub">端口 9000 · v{{ serviceVersion }}</div>
         </el-card>
       </el-col>
-      <el-col :span="6">
-        <el-card shadow="hover">
-          <div class="status-icon" style="background:linear-gradient(135deg,#11998e,#38ef7d)">📊</div>
-          <div class="status-info">
-            <div class="status-label">API 端点数</div>
-            <div class="status-value">{{ apiCount }}</div>
-            <div class="status-sub">个可用接口</div>
-          </div>
+      <el-col :span="4">
+        <el-card shadow="hover" class="mini-card">
+          <div class="mini-icon" style="background:linear-gradient(135deg,#11998e,#38ef7d)">📡</div>
+          <div class="mini-text">{{ apiCount }} 个端点</div>
+          <div class="mini-sub">API 接口总数</div>
         </el-card>
       </el-col>
-      <el-col :span="6">
-        <el-card shadow="hover">
-          <div class="status-icon" style="background:linear-gradient(135deg,#fc4a1a,#f7b733)">⚡</div>
-          <div class="status-info">
-            <div class="status-label">刷新操作</div>
-            <div style="margin-top:6px">
-              <el-button size="mini" type="primary" @click="refreshAll" :loading="refreshing">刷新</el-button>
-            </div>
-          </div>
+      <el-col :span="4">
+        <el-card shadow="hover" class="mini-card">
+          <div class="mini-icon" style="background:linear-gradient(135deg,#fc4a1a,#f7b733)">📋</div>
+          <div class="mini-text">{{ totalLogs }} 条日志</div>
+          <div class="mini-sub">请求历史记录</div>
         </el-card>
       </el-col>
+      <el-col :span="8" class="top-actions">
+        <el-button size="small" type="primary" @click="refreshAll" :loading="refreshing">🔄 刷新</el-button>
+        <el-button size="small" @click="fetchLogs">📋 拉取日志</el-button>
+        <el-button size="small" type="danger" @click="clearLogsConfirm" :disabled="!logList.length">清空日志</el-button>
+      </el-col>
     </el-row>
 
-    <!-- 组件健康状态 -->
-    <el-card class="component-card" shadow="never">
-      <div slot="header" class="card-header">
-        <span>🧩 组件健康状态</span>
-        <el-button size="mini" @click="fetchDetailedHealth">重新检测</el-button>
-      </div>
-      <el-table :data="componentList" border stripe size="small">
-        <el-table-column prop="name" label="组件" width="160" />
-        <el-table-column prop="status" label="状态" width="100">
-          <template slot-scope="{ row }">
-            <el-tag :type="row.status === 'ok' ? 'success' : row.status === 'error' ? 'danger' : 'info'" size="small">
-              {{ row.status === 'ok' ? '正常' : row.status === 'error' ? '异常' : '未知' }}
-            </el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="detail" label="详情" />
-      </el-table>
-    </el-card>
+    <!-- 标签页 -->
+    <el-tabs v-model="activeTab" type="border-card" class="main-tabs">
+      
+      <!-- ===== 概览 ===== -->
+      <el-tab-pane label="📊 概览" name="overview">
+        <el-row :gutter="16">
+          <el-col :span="16">
+            <el-card shadow="never" class="panel">
+              <div slot="header"><span>🧩 组件健康状态</span></div>
+              <el-table :data="componentList" border stripe size="small" max-height="300">
+                <el-table-column prop="name" label="组件" width="140" />
+                <el-table-column prop="status" label="状态" width="100">
+                  <template slot-scope="{ row }">
+                    <el-tag :type="row.status === 'ok' ? 'success' : row.status === 'error' ? 'danger' : 'info'" size="small">
+                      {{ row.status === 'ok' ? '正常' : row.status === 'error' ? '异常' : '未知' }}
+                    </el-tag>
+                  </template>
+                </el-table-column>
+                <el-table-column prop="detail" label="详情" show-overflow-tooltip />
+              </el-table>
+            </el-card>
+          </el-col>
+          <el-col :span="8">
+            <el-card shadow="never" class="panel">
+              <div slot="header"><span>⚙️ 服务配置</span></div>
+              <el-descriptions :column="1" border size="small">
+                <el-descriptions-item label="LLM 模型">{{ config.LLM_MODEL || '-' }}</el-descriptions-item>
+                <el-descriptions-item label="LLM Base URL">{{ config.LLM_BASE_URL || '-' }}</el-descriptions-item>
+                <el-descriptions-item label="Embedding 模型">{{ config.EMBEDDING_MODEL || '-' }}</el-descriptions-item>
+                <el-descriptions-item label="Embedding URL">{{ config.EMBEDDING_BASE_URL || '-' }}</el-descriptions-item>
+                <el-descriptions-item label="Java 后端">{{ config.JAVA_BASE_URL || '-' }}</el-descriptions-item>
+                <el-descriptions-item label="Chroma DB">{{ config.CHROMA_DB_PATH || '-' }}</el-descriptions-item>
+                <el-descriptions-item label="LangSmith 追踪">
+                  <el-tag :type="config.LANGCHAIN_TRACING_V2 === 'true' ? 'success' : 'info'" size="mini">
+                    {{ config.LANGCHAIN_TRACING_V2 === 'true' ? '已启用' : '未启用' }}
+                  </el-tag>
+                </el-descriptions-item>
+                <el-descriptions-item label="日志级别">{{ config.LOG_LEVEL || 'info' }}</el-descriptions-item>
+              </el-descriptions>
+            </el-card>
+          </el-col>
+        </el-row>
+      </el-tab-pane>
+
+      <!-- ===== API 端点 ===== -->
+      <el-tab-pane label="📡 API 端点" name="apis">
+        <div class="tab-toolbar">
+          <el-input v-model="apiFilter" placeholder="筛选路径或说明..." size="small" prefix-icon="el-icon-search" style="width:280px" clearable />
+          <el-button size="small" @click="fetchSpec">刷新列表</el-button>
+        </div>
+        <el-table :data="filteredApiEndpoints" border stripe size="small" max-height="500" style="width:100%">
+          <el-table-column prop="method" label="方法" width="80" fixed>
+            <template slot-scope="{ row }">
+              <el-tag :type="row.method === 'GET' ? 'info' : 'primary'" size="mini">{{ row.method }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column prop="path" label="路径" min-width="260" show-overflow-tooltip />
+          <el-table-column prop="summary" label="说明" min-width="180" show-overflow-tooltip />
+          <el-table-column label="操作" width="80" fixed="right">
+            <template slot-scope="{ row }">
+              <el-button v-if="row.method === 'POST'" type="text" size="mini" @click="openTestDialog(row)">测试</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-tab-pane>
 
-    <!-- API 端点列表 -->
-    <el-card class="api-card" shadow="never">
-      <div slot="header" class="card-header">
-        <span>📡 API 端点列表 ({{ apiEndpoints.length }})</span>
-        <el-input v-model="apiFilter" placeholder="筛选端点..." size="mini" prefix-icon="el-icon-search" style="width:200px" clearable />
-      </div>
-      <el-table :data="filteredApiEndpoints" border stripe size="small" max-height="700">
-        <el-table-column prop="method" label="方法" width="80">
-          <template slot-scope="{ row }">
-            <el-tag :type="row.method === 'GET' ? 'info' : 'primary'" size="mini">{{ row.method }}</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="path" label="路径" min-width="280" show-overflow-tooltip />
-        <el-table-column prop="summary" label="说明" min-width="200" show-overflow-tooltip />
-        <el-table-column label="操作" width="100" fixed="right">
-          <template slot-scope="{ row }">
-            <el-button v-if="row.method === 'POST'" type="text" size="small" @click="openTestDialog(row)">测试</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-    </el-card>
+      <!-- ===== 请求历史 ===== -->
+      <el-tab-pane label="📋 请求历史" name="logs">
+        <div class="tab-toolbar">
+          <el-select v-model="logFilterMethod" placeholder="方法筛选" size="small" clearable style="width:100px;margin-right:8px">
+            <el-option label="GET" value="GET" />
+            <el-option label="POST" value="POST" />
+          </el-select>
+          <el-input v-model="logFilterPath" placeholder="路径筛选..." size="small" prefix-icon="el-icon-search" style="width:200px;margin-right:8px" clearable />
+          <el-tag size="small" type="info">共 {{ totalLogs }} 条</el-tag>
+          <el-button size="small" @click="fetchLogs" :loading="logLoading">刷新</el-button>
+          <el-button size="small" type="danger" @click="clearLogsConfirm" :disabled="!logList.length">清空</el-button>
+        </div>
+        <el-table :data="filteredLogs" border stripe size="small" max-height="520" style="width:100%">
+          <el-table-column prop="ts" label="时间" width="70" fixed />
+          <el-table-column prop="method" label="方法" width="60">
+            <template slot-scope="{ row }">
+              <el-tag :type="row.method === 'GET' ? 'info' : 'primary'" size="mini">{{ row.method }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column prop="path" label="路径" min-width="200" show-overflow-tooltip />
+          <el-table-column prop="status" label="状态" width="60">
+            <template slot-scope="{ row }">
+              <el-tag :type="row.status < 400 ? 'success' : 'danger'" size="mini">{{ row.status }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column prop="duration_ms" label="耗时" width="70">
+            <template slot-scope="{ row }">
+              <span :style="{ color: row.duration_ms > 5000 ? '#f56c6c' : '' }">{{ row.duration_ms }}ms</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="client_ip" label="IP" width="90" show-overflow-tooltip />
+          <el-table-column label="操作" width="60" fixed="right">
+            <template slot-scope="{ row }">
+              <el-button type="text" size="mini" @click="toggleLogDetail(row)">详情</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+        <!-- 详情展开行 -->
+        <el-table :data="logDetailRows" border stripe size="small" max-height="300" class="log-detail-table" v-if="expandedLog">
+          <el-table-column label="请求时间" width="100"><template slot-scope="{ row }">{{ row.ts }}</template></el-table-column>
+          <el-table-column label="方法"><template slot-scope="{ row }">{{ row.method }}</template></el-table-column>
+          <el-table-column label="路径"><template slot-scope="{ row }">{{ row.path }}</template></el-table-column>
+          <el-table-column label="状态"><template slot-scope="{ row }"><el-tag :type="row.status < 400 ? 'success' : 'danger'" size="mini">{{ row.status }}</el-tag></template></el-table-column>
+          <el-table-column label="耗时(ms)"><template slot-scope="{ row }">{{ row.duration_ms }}</template></el-table-column>
+          <el-table-column label="客户端IP"><template slot-scope="{ row }">{{ row.client_ip }}</template></el-table-column>
+          <el-table-column label="请求体" min-width="300">
+            <template slot-scope="{ row }">
+              <pre class="detail-pre">{{ row.body || '(空)' }}</pre>
+            </template>
+          </el-table-column>
+          <el-table-column label="响应体" min-width="300">
+            <template slot-scope="{ row }">
+              <pre class="detail-pre">{{ (row.response || '').substring(0, 2000) }}</pre>
+            </template>
+          </el-table-column>
+          <el-table-column label="错误" min-width="200" v-if="row.error">
+            <template slot-scope="{ row }">
+              <pre class="detail-pre error-pre">{{ row.error }}</pre>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-tab-pane>
 
-    <!-- 配置面板 -->
-    <el-card class="config-card" shadow="never">
-      <div slot="header" class="card-header">
-        <span>⚙️ 服务配置</span>
-        <el-button size="mini" type="primary" @click="fetchConfig">刷新配置</el-button>
-      </div>
-      <el-descriptions :column="2" border size="small">
-        <el-descriptions-item label="LLM 模型" :span="1">
-          <el-tag size="mini">{{ config.LLM_MODEL || '-' }}</el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="LLM 基础 URL" :span="1">
-          {{ config.LLM_BASE_URL || '-' }}
-        </el-descriptions-item>
-        <el-descriptions-item label="Embedding 模型" :span="1">
-          <el-tag size="mini">{{ config.EMBEDDING_MODEL || '-' }}</el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="Embedding URL" :span="1">
-          {{ config.EMBEDDING_BASE_URL || '-' }}
-        </el-descriptions-item>
-        <el-descriptions-item label="Java 后端地址" :span="1">
-          {{ config.JAVA_BASE_URL || '-' }}
-        </el-descriptions-item>
-        <el-descriptions-item label="Chroma DB 路径" :span="1">
-          {{ config.CHROMA_DB_PATH || '-' }}
-        </el-descriptions-item>
-        <el-descriptions-item label="LangSmith 追踪" :span="1">
-          <el-tag :type="config.LANGCHAIN_TRACING_V2 === 'true' ? 'success' : 'info'" size="mini">
-            {{ config.LANGCHAIN_TRACING_V2 === 'true' ? '已启用' : '未启用' }}
-          </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="日志级别" :span="1">
-          <el-tag size="mini">{{ config.LOG_LEVEL || 'info' }}</el-tag>
-        </el-descriptions-item>
-      </el-descriptions>
-    </el-card>
+    </el-tabs>
 
     <!-- API 测试对话框 -->
-    <el-dialog :visible.sync="testDialogVisible" :title="'测试: ' + testingEndpoint.path" width="700px" :close-on-click-modal="false">
+    <el-dialog :visible.sync="testDialogVisible" :title="'测试: ' + testingEndpoint.path" width="720px" :close-on-click-modal="false">
       <el-form size="small">
         <el-form-item label="请求参数">
-          <el-input
-            v-model="testParams"
-            type="textarea"
-            :rows="8"
-            placeholder='请输入 JSON 参数,例如: {"user_id": 1, "query": "你好"}'
-          />
+          <el-input v-model="testParams" type="textarea" :rows="6"
+            placeholder='请输入 JSON 参数,例如: {"user_id": 1, "query": "你好"}' />
         </el-form-item>
         <el-form-item>
           <el-button type="primary" @click="runTest" :loading="testing">发送请求</el-button>
@@ -142,25 +184,6 @@
       <pre class="response-pre" v-if="testResponse">{{ testResponse }}</pre>
       <el-alert v-else type="info" :closable="false" description="点击"发送请求"查看响应结果" />
     </el-dialog>
-
-    <!-- 日志面板 -->
-    <el-card class="log-card" shadow="never">
-      <div slot="header" class="card-header">
-        <span>📋 最近日志</span>
-        <div>
-          <el-button size="mini" @click="fetchLogs" :loading="logLoading">刷新</el-button>
-          <el-button size="mini" type="danger" @click="clearLogs">清空</el-button>
-        </div>
-      </div>
-      <div class="log-container" ref="logContainer">
-        <div v-for="(log, i) in logList" :key="i" class="log-line" :class="'log-' + getLogLevel(log)">
-          <span class="log-time">{{ log.time }}</span>
-          <span class="log-level">{{ log.level }}</span>
-          <span class="log-msg">{{ log.msg }}</span>
-        </div>
-        <div v-if="logList.length === 0" class="log-empty">暂无日志</div>
-      </div>
-    </el-card>
   </div>
 </template>
 
@@ -170,44 +193,32 @@ const BASE = 'https://ai.etotem.com.cn'
 function fetchJSON(url, options = {}) {
   return fetch(url, {
     ...options,
-    headers: {
-      'Content-Type': 'application/json',
-      ...options.headers
-    }
+    headers: { 'Content-Type': 'application/json', ...options.headers }
   }).then(function(res) {
     if (!res.ok) throw new Error('HTTP ' + res.status)
     return res.json()
   })
 }
 
-function fetchText(url, options = {}) {
-  return fetch(url, {
-    ...options,
-    headers: {
-      'Content-Type': 'application/json',
-      ...options.headers
-    }
-  }).then(function(res) {
-    if (!res.ok) throw new Error('HTTP ' + res.status)
-    return res.text()
-  })
-}
-
 export default {
   name: 'LangGraphAdmin',
   data() {
     return {
+      activeTab: 'overview',
       langgraphUrl: 'https://ai.etotem.com.cn',
       serviceVersion: '0.3.0',
+      // 状态
       statusText: '检测中...',
       statusClass: 'status-checking',
       statusIcon: '🔄',
       statusTime: '',
       apiCount: 0,
+      // API 列表
       apiEndpoints: [],
       apiFilter: '',
+      // 组件健康
       componentList: [],
-      refreshing: false,
+      // 配置
       config: {
         LLM_MODEL: 'deepseek',
         LLM_BASE_URL: 'http://axonhub.iwintrue.com/v1',
@@ -218,13 +229,20 @@ export default {
         LANGCHAIN_TRACING_V2: 'true',
         LOG_LEVEL: 'info'
       },
+      // 请求日志
+      logList: [],
+      totalLogs: 0,
+      logFilterMethod: '',
+      logFilterPath: '',
+      logLoading: false,
+      expandedLog: null,
+      // 测试
       testDialogVisible: false,
       testingEndpoint: {},
       testParams: '{}',
       testing: false,
       testResponse: '',
-      logList: [],
-      logLoading: false,
+      refreshing: false,
       _interval: null
     }
   },
@@ -236,11 +254,21 @@ export default {
       return self.apiEndpoints.filter(function(e) {
         return e.path.toLowerCase().indexOf(kw) >= 0 || (e.summary && e.summary.toLowerCase().indexOf(kw) >= 0)
       })
+    },
+    filteredLogs: function() {
+      var list = this.logList
+      if (this.logFilterMethod) {
+        list = list.filter(function(l) { return l.method === this.logFilterMethod }, this)
+      }
+      if (this.logFilterPath) {
+        var kw = this.logFilterPath.toLowerCase()
+        list = list.filter(function(l) { return l.path.toLowerCase().indexOf(kw) >= 0 })
+      }
+      return list
     }
   },
   created: function() {
     this.refreshAll()
-    this.fetchLogs()
     this._interval = setInterval(this.refreshAll, 60000)
   },
   beforeDestroy: function() {
@@ -284,7 +312,6 @@ export default {
         var res = await fetchJSON(BASE + '/openapi.json')
         var paths = res.paths || {}
         var endpoints = []
-        var self = this
         Object.keys(paths).forEach(function(path) {
           var methods = paths[path]
           Object.keys(methods).forEach(function(method) {
@@ -323,9 +350,6 @@ export default {
           comps.push({ name: 'Java 后端', status: c.java_backend.status || 'unknown', detail: c.java_backend.code ? 'HTTP ' + c.java_backend.code : (c.java_backend.message || '') })
         }
         this.componentList = comps
-
-        // 状态判定:仅当 LLM 异常时才显示异常
-        // Java 后端通过 Docker 容器访问时可能网络不通(内外网 IP 差异),不作为整体状态依据
         var llmOk = !c.llm || c.llm.status === 'ok'
         var chromaOk = !c.chromadb || c.chromadb.status === 'ok'
         if (llmOk && chromaOk) {
@@ -346,18 +370,39 @@ export default {
         this.componentList = [{ name: '健康检查', status: 'error', detail: e.message }]
       }
     },
-    fetchConfig: function() {
-      this.config = {
-        LLM_MODEL: 'deepseek',
-        LLM_BASE_URL: 'http://axonhub.iwintrue.com/v1',
-        EMBEDDING_MODEL: 'BAAI/bge-large-zh-v1.5',
-        EMBEDDING_BASE_URL: 'https://api.siliconflow.cn/v1',
-        JAVA_BASE_URL: 'http://111.227.6.214:9082',
-        CHROMA_DB_PATH: '/data/chroma_db',
-        LANGCHAIN_TRACING_V2: 'true',
-        LOG_LEVEL: 'info'
+    // ===== 请求日志 =====
+    async fetchLogs() {
+      this.logLoading = true
+      try {
+        var res = await fetchJSON(BASE + '/api/v1/logs?limit=200')
+        this.logList = res.logs || []
+        this.totalLogs = res.total || 0
+      } catch (e) {
+        this.logList = []
+        this.totalLogs = 0
+        this.$message.error('获取日志失败: ' + e.message)
+      } finally {
+        this.logLoading = false
       }
     },
+    async clearLogsConfirm() {
+      var self = this
+      this.$confirm('确认清空所有请求日志?', '提示', { type: 'warning' }).then(async function() {
+        try {
+          var res = await fetch(BASE + '/api/v1/logs/clear', { method: 'POST' })
+          var data = await res.json()
+          self.logList = []
+          self.totalLogs = 0
+          self.$message.success('已清空 ' + (data.cleared || 0) + ' 条日志')
+        } catch (e) {
+          self.$message.error('清空失败: ' + e.message)
+        }
+      }).catch(function() {})
+    },
+    toggleLogDetail: function(row) {
+      this.expandedLog = this.expandedLog === row ? null : row
+    },
+    // ===== API 测试 =====
     openTestDialog: function(endpoint) {
       this.testingEndpoint = endpoint
       var defaults = {
@@ -396,34 +441,6 @@ export default {
       } finally {
         self.testing = false
       }
-    },
-    async fetchLogs() {
-      this.logLoading = true
-      try {
-        var res = await fetch(BASE + '/openapi.json')
-        var data = await res.json()
-        this.logList = [{
-          time: new Date().toLocaleTimeString(),
-          level: 'INFO',
-          msg: '服务连接正常,API 端点数: ' + Object.keys(data.paths || {}).length
-        }]
-      } catch (e) {
-        this.logList = [{
-          time: new Date().toLocaleTimeString(),
-          level: 'ERROR',
-          msg: '无法连接到 LangGraph 服务: ' + e.message
-        }]
-      } finally {
-        this.logLoading = false
-      }
-    },
-    clearLogs: function() {
-      this.logList = []
-    },
-    getLogLevel: function(log) {
-      if (log.level === 'ERROR' || log.level === 'error') return 'error'
-      if (log.level === 'WARNING' || log.level === 'WARN') return 'warning'
-      return 'info'
     }
   }
 }
@@ -432,51 +449,77 @@ export default {
 <style scoped>
 .page-container {
   padding: 16px;
+  background: #f0f2f5;
+  min-height: 100vh;
 }
-.status-row {
-  margin-bottom: 16px;
+
+/* 顶部状态栏 */
+.top-bar {
+  margin-bottom: 12px;
 }
-.status-card {
+.mini-card {
   display: flex;
+  flex-direction: column;
   align-items: center;
-  padding: 12px 16px;
+  justify-content: center;
+  padding: 12px 8px;
+  text-align: center;
   cursor: default;
   transition: all 0.3s;
+  border-left: 4px solid transparent;
 }
-.status-card:hover {
+.mini-card:hover {
   transform: translateY(-2px);
 }
-.status-healthy { border-left: 4px solid #67c23a; }
-.status-unhealthy { border-left: 4px solid #f56c6c; }
-.status-degraded { border-left: 4px solid #e6a23c; }
-.status-checking { border-left: 4px solid #409eff; }
-.status-icon {
-  font-size: 32px;
-  margin-right: 16px;
-}
-.status-info {
-  flex: 1;
-}
-.status-label {
-  font-size: 12px;
-  color: #909399;
+.mini-card.status-healthy { border-left-color: #67c23a; }
+.mini-card.status-unhealthy { border-left-color: #f56c6c; }
+.mini-card.status-degraded { border-left-color: #e6a23c; }
+.mini-card.status-checking { border-left-color: #409eff; }
+.mini-icon {
+  font-size: 24px;
   margin-bottom: 4px;
 }
-.status-value {
-  font-size: 18px;
-  font-weight: bold;
+.mini-text {
+  font-size: 14px;
+  font-weight: 600;
   color: #303133;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 100%;
 }
-.status-sub {
-  font-size: 12px;
+.mini-sub {
+  font-size: 11px;
   color: #909399;
   margin-top: 2px;
 }
-.card-header {
+.top-actions {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  justify-content: flex-start;
+  padding-left: 16px;
+}
+
+/* 标签页 */
+.main-tabs {
+  border-radius: 8px;
+  overflow: hidden;
+}
+.tab-toolbar {
   display: flex;
-  justify-content: space-between;
   align-items: center;
+  gap: 8px;
+  margin-bottom: 12px;
+  flex-wrap: wrap;
 }
+
+/* 面板 */
+.panel {
+  margin-bottom: 16px;
+}
+
+/* 响应体 */
 .response-pre {
   background: #f5f7fa;
   padding: 12px;
@@ -487,26 +530,26 @@ export default {
   white-space: pre-wrap;
   word-break: break-all;
 }
-.log-container {
-  max-height: 600px;
-  overflow-y: auto;
+
+/* 日志详情 */
+.log-detail-table {
+  margin-top: 8px;
+  border-radius: 4px;
+  overflow: hidden;
+}
+.detail-pre {
   background: #1e1e1e;
+  color: #d4d4d4;
+  padding: 8px;
   border-radius: 4px;
-  padding: 12px;
-  font-family: 'Courier New', monospace;
-  font-size: 12px;
+  font-size: 11px;
+  max-height: 200px;
+  overflow-y: auto;
+  white-space: pre-wrap;
+  word-break: break-all;
+  margin: 0;
 }
-.log-line {
-  display: flex;
-  gap: 12px;
-  padding: 2px 0;
-  line-height: 1.6;
+.error-pre {
+  color: #f56c6c;
 }
-.log-time { color: #888; min-width: 80px; }
-.log-level { min-width: 60px; font-weight: bold; }
-.log-level.ERROR { color: #f56c6c; }
-.log-level.WARNING { color: #e6a23c; }
-.log-level.INFO { color: #67c23a; }
-.log-msg { color: #d4d4d4; flex: 1; }
-.log-empty { color: #666; text-align: center; padding: 20px; }
 </style>