openclaw-agent-dashboard 1.0.21 → 1.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +55 -321
  2. package/frontend-dist/assets/index-B7XqKAxm.css +1 -0
  3. package/frontend-dist/assets/index-CxJaSYyo.js +24 -0
  4. package/{frontend → frontend-dist}/index.html +2 -1
  5. package/{plugin/openclaw.plugin.json → openclaw.plugin.json} +2 -2
  6. package/package.json +21 -13
  7. package/.github/workflows/release.yml +0 -56
  8. package/VERSION_DISPLAY_delivery.md +0 -242
  9. package/VERSION_DISPLAY_implementation_summary.md +0 -315
  10. package/design_manifest.md +0 -100
  11. package/docs/CHANGELOG_AGENT_MODIFICATIONS.md +0 -132
  12. package/docs/MAINTAINER_RELEASE_WORKFLOW.md +0 -211
  13. package/docs/Openclaw-Agent-Dashboard/345/217/221/345/270/203/344/270/216/346/233/264/346/226/260.md +0 -147
  14. package/docs/RELEASE-LATEST.md +0 -189
  15. package/docs/RELEASE-MODEL-CONFIG.md +0 -95
  16. package/docs/WINDOWS_INSTALL_TROUBLESHOOTING.md +0 -171
  17. package/docs/design/.gitkeep +0 -0
  18. package/docs/design/VERSION_DISPLAY_design.md +0 -1236
  19. package/docs/release-guide.md +0 -259
  20. package/docs/release-operations-manual.md +0 -167
  21. package/docs/reviews/.gitkeep +0 -0
  22. package/docs/reviews/approval_history.json +0 -14
  23. package/docs/reviews/cr_VERSION_DISPLAY.md +0 -397
  24. package/docs/reviews/traceability_manifest.json +0 -279
  25. package/docs/specs/VERSION_DISPLAY_spec.md +0 -371
  26. package/docs/specs/tr3-install-system.md +0 -580
  27. package/docs/windows-collaboration-model-paths-troubleshooting.md +0 -0
  28. package/frontend/package-lock.json +0 -1240
  29. package/frontend/package.json +0 -19
  30. package/frontend/src/App.vue +0 -355
  31. package/frontend/src/components/AgentCard.vue +0 -796
  32. package/frontend/src/components/AgentConfigPanel.vue +0 -539
  33. package/frontend/src/components/AgentDetailPanel.vue +0 -738
  34. package/frontend/src/components/ErrorAnalysisView.vue +0 -546
  35. package/frontend/src/components/ErrorCenterPanel.vue +0 -844
  36. package/frontend/src/components/PerformanceMonitor.vue +0 -515
  37. package/frontend/src/components/SettingsPanel.vue +0 -236
  38. package/frontend/src/components/TokenAnalysisPanel.vue +0 -683
  39. package/frontend/src/components/chain/ChainEdge.vue +0 -85
  40. package/frontend/src/components/chain/ChainNode.vue +0 -166
  41. package/frontend/src/components/chain/TaskChainView.vue +0 -425
  42. package/frontend/src/components/chain/index.ts +0 -3
  43. package/frontend/src/components/chain/types.ts +0 -70
  44. package/frontend/src/components/collaboration/CollaborationFlowSection.vue +0 -1032
  45. package/frontend/src/components/collaboration/CollaborationFlowWrapper.vue +0 -113
  46. package/frontend/src/components/common/VersionDisplay.vue +0 -187
  47. package/frontend/src/components/performance/PerformancePanel.vue +0 -119
  48. package/frontend/src/components/performance/PerformanceSection.vue +0 -1137
  49. package/frontend/src/components/tasks/TaskStatusSection.vue +0 -973
  50. package/frontend/src/components/timeline/TimelineConnector.vue +0 -31
  51. package/frontend/src/components/timeline/TimelineRound.vue +0 -135
  52. package/frontend/src/components/timeline/TimelineStep.vue +0 -691
  53. package/frontend/src/components/timeline/TimelineToolLink.vue +0 -109
  54. package/frontend/src/components/timeline/TimelineView.vue +0 -540
  55. package/frontend/src/components/timeline/index.ts +0 -5
  56. package/frontend/src/components/timeline/types.ts +0 -120
  57. package/frontend/src/composables/index.ts +0 -7
  58. package/frontend/src/composables/useDebounce.ts +0 -48
  59. package/frontend/src/composables/useRealtime.ts +0 -52
  60. package/frontend/src/composables/useState.ts +0 -52
  61. package/frontend/src/composables/useThrottle.ts +0 -46
  62. package/frontend/src/composables/useVirtualScroll.ts +0 -106
  63. package/frontend/src/main.ts +0 -4
  64. package/frontend/src/managers/EventDispatcher.ts +0 -127
  65. package/frontend/src/managers/RealtimeDataManager.ts +0 -302
  66. package/frontend/src/managers/StateManager.ts +0 -128
  67. package/frontend/src/managers/index.ts +0 -5
  68. package/frontend/src/types/collaboration.ts +0 -135
  69. package/frontend/src/types/index.ts +0 -20
  70. package/frontend/src/types/performance.ts +0 -105
  71. package/frontend/src/types/task.ts +0 -38
  72. package/frontend/vite.config.ts +0 -18
  73. package/legacy_code_anatomy.md +0 -518
  74. package/plugin/README.md +0 -99
  75. package/plugin/config.json.example +0 -1
  76. package/plugin/package.json +0 -26
  77. package/scripts/build-plugin.js +0 -81
  78. package/scripts/bundle.sh +0 -62
  79. package/scripts/install-plugin.sh +0 -162
  80. package/scripts/install-python-deps.sh +0 -226
  81. package/scripts/install.js +0 -684
  82. package/scripts/install.sh +0 -367
  83. package/scripts/lib/common.sh +0 -137
  84. package/scripts/release-pack.sh +0 -110
  85. package/scripts/start.js +0 -50
  86. package/scripts/test_available_models.py +0 -284
  87. package/scripts/test_version_display.sh +0 -128
  88. package/scripts/test_websocket_ping.py +0 -44
  89. package/session_registry.json +0 -58
  90. package/tests/.gitkeep +0 -0
  91. package/tests/qa_regression_report.md +0 -359
  92. package/tests/qa_version_display_report.md +0 -598
  93. /package/{src/backend → dashboard}/agents.py +0 -0
  94. /package/{src/backend → dashboard}/api/__init__.py +0 -0
  95. /package/{src/backend → dashboard}/api/agent_config_api.py +0 -0
  96. /package/{src/backend → dashboard}/api/agents.py +0 -0
  97. /package/{src/backend → dashboard}/api/agents_config.py +0 -0
  98. /package/{src/backend → dashboard}/api/chains.py +0 -0
  99. /package/{src/backend → dashboard}/api/collaboration.py +0 -0
  100. /package/{src/backend → dashboard}/api/debug_paths.py +0 -0
  101. /package/{src/backend → dashboard}/api/error_analysis.py +0 -0
  102. /package/{src/backend → dashboard}/api/errors.py +0 -0
  103. /package/{src/backend → dashboard}/api/performance.py +0 -0
  104. /package/{src/backend → dashboard}/api/subagents.py +0 -0
  105. /package/{src/backend → dashboard}/api/timeline.py +0 -0
  106. /package/{src/backend → dashboard}/api/version.py +0 -0
  107. /package/{src/backend → dashboard}/api/websocket.py +0 -0
  108. /package/{src/backend → dashboard}/collaboration.py +0 -0
  109. /package/{src/backend → dashboard}/data/__init__.py +0 -0
  110. /package/{src/backend → dashboard}/data/agent_config_manager.py +0 -0
  111. /package/{src/backend → dashboard}/data/chain_reader.py +0 -0
  112. /package/{src/backend → dashboard}/data/config_reader.py +0 -0
  113. /package/{src/backend → dashboard}/data/error_analyzer.py +0 -0
  114. /package/{src/backend → dashboard}/data/session_reader.py +0 -0
  115. /package/{src/backend → dashboard}/data/subagent_reader.py +0 -0
  116. /package/{src/backend → dashboard}/data/task_history.py +0 -0
  117. /package/{src/backend → dashboard}/data/timeline_reader.py +0 -0
  118. /package/{src/backend → dashboard}/data/version_info_reader.py +0 -0
  119. /package/{src/backend → dashboard}/errors.py +0 -0
  120. /package/{src/backend → dashboard}/main.py +0 -0
  121. /package/{src/backend → dashboard}/mechanism_reader.py +0 -0
  122. /package/{src/backend → dashboard}/mechanisms.py +0 -0
  123. /package/{src/backend → dashboard}/performance.py +0 -0
  124. /package/{src/backend → dashboard}/requirements.txt +0 -0
  125. /package/{src/backend → dashboard}/session_reader.py +0 -0
  126. /package/{src/backend → dashboard}/status/__init__.py +0 -0
  127. /package/{src/backend → dashboard}/status/change_tracker.py +0 -0
  128. /package/{src/backend → dashboard}/status/error_detector.py +0 -0
  129. /package/{src/backend → dashboard}/status/status_cache.py +0 -0
  130. /package/{src/backend → dashboard}/status/status_calculator.py +0 -0
  131. /package/{src/backend → dashboard}/status_calculator.py +0 -0
  132. /package/{src/backend → dashboard}/subagent_reader.py +0 -0
  133. /package/{src/backend → dashboard}/watchers/__init__.py +0 -0
  134. /package/{src/backend → dashboard}/watchers/file_watcher.py +0 -0
  135. /package/{plugin/index.js → index.js} +0 -0
@@ -1,515 +0,0 @@
1
- <template>
2
- <div class="performance-monitor">
3
- <div class="header-row">
4
- <h2>性能监控</h2>
5
- </div>
6
-
7
- <div class="metrics-grid">
8
- <div class="metric-card">
9
- <div class="metric-label">当前 TPM</div>
10
- <div class="metric-value">{{ currentTpm }}</div>
11
- <div class="metric-unit">Tokens/分钟</div>
12
- <div class="metric-desc">每分钟消耗的 Token 数</div>
13
- </div>
14
-
15
- <div class="metric-card">
16
- <div class="metric-label">当前 RPM</div>
17
- <div class="metric-value">{{ currentRpm }}</div>
18
- <div class="metric-unit">Requests/分钟</div>
19
- <div class="metric-desc">每分钟 API 请求数</div>
20
- </div>
21
-
22
- <div class="metric-card">
23
- <div class="metric-label">API 响应时间</div>
24
- <div class="metric-value">{{ apiLatency }}ms</div>
25
- <div class="metric-unit">毫秒</div>
26
- <div class="metric-desc">API 平均响应时间</div>
27
- </div>
28
-
29
- <div class="metric-card">
30
- <div class="metric-label">WebSocket 连接</div>
31
- <div class="metric-value">{{ connectionsCount }}</div>
32
- <div class="metric-unit">个</div>
33
- <div class="metric-desc">当前活跃连接数</div>
34
- </div>
35
- </div>
36
-
37
- <div class="charts-container">
38
- <!-- TPM 图表 - 上方 -->
39
- <div class="chart-card" data-type="tpm">
40
- <div class="chart-header">
41
- <h3>TPM 趋势 (最近20分钟)</h3>
42
- <span class="chart-datetime">{{ currentDateTime }}</span>
43
- </div>
44
- <div class="chart-wrapper">
45
- <div class="chart-bars">
46
- <div
47
- v-for="(value, index) in tpmHistory"
48
- :key="`tpm-${index}`"
49
- class="chart-bar"
50
- :data-value="value"
51
- >
52
- <!-- 数字标签 - 始终显示在柱子上方 -->
53
- <span class="bar-value">{{ formatNumber(value) }}</span>
54
-
55
- <!-- 柱子本体 -->
56
- <div
57
- class="bar-visual"
58
- :style="{ height: `${getBarHeight(value, maxTpm)}%` }"
59
- ></div>
60
-
61
- <!-- 时间标签 - 每个都显示 -->
62
- <span class="bar-time-label">
63
- {{ formatTimestamp(timestamps[index]) }}
64
- </span>
65
- </div>
66
- </div>
67
- </div>
68
- </div>
69
-
70
- <!-- RPM 图表 - 下方 -->
71
- <div class="chart-card" data-type="rpm">
72
- <div class="chart-header">
73
- <h3>RPM 趋势 (最近20分钟)</h3>
74
- <span class="chart-datetime">{{ currentDateTime }}</span>
75
- </div>
76
- <div class="chart-wrapper">
77
- <div class="chart-bars">
78
- <div
79
- v-for="(value, index) in rpmHistory"
80
- :key="`rpm-${index}`"
81
- class="chart-bar"
82
- :data-value="value"
83
- >
84
- <!-- 数字标签 - 始终显示在柱子上方 -->
85
- <span class="bar-value">{{ formatNumber(value) }}</span>
86
-
87
- <!-- 柱子本体 -->
88
- <div
89
- class="bar-visual"
90
- :style="{ height: `${getBarHeight(value, maxRpm)}%` }"
91
- ></div>
92
-
93
- <!-- 时间标签 - 每个都显示 -->
94
- <span class="bar-time-label">
95
- {{ formatTimestamp(timestamps[index]) }}
96
- </span>
97
- </div>
98
- </div>
99
- </div>
100
- </div>
101
- </div>
102
-
103
- <div class="stats-summary">
104
- <h3>总计 (最近20分钟)</h3>
105
- <div class="summary-grid">
106
- <div class="summary-item">
107
- <span class="summary-label">总 Token:</span>
108
- <span class="summary-value">{{ formatNumber(totalTokens) }}</span>
109
- <span class="summary-unit">tokens</span>
110
- </div>
111
- <div class="summary-item">
112
- <span class="summary-label">总请求:</span>
113
- <span class="summary-value">{{ formatNumber(totalRequests) }}</span>
114
- <span class="summary-unit">次</span>
115
- </div>
116
- </div>
117
- </div>
118
- </div>
119
- </template>
120
-
121
- <script setup lang="ts">
122
- import { ref, computed, onMounted, onUnmounted } from 'vue'
123
-
124
- const connectionsCount = ref(0)
125
- const apiLatency = ref(0)
126
- const currentTpm = ref(0)
127
- const currentRpm = ref(0)
128
- const tpmHistory = ref<number[]>([])
129
- const rpmHistory = ref<number[]>([])
130
- const timestamps = ref<(string | number)[]>([])
131
- const maxTpm = ref(100)
132
- const maxRpm = ref(10)
133
- const totalTokens = ref(0)
134
- const totalRequests = ref(0)
135
- const currentDateTime = ref('')
136
-
137
- let monitorInterval: any = null
138
- let dateTimeInterval: any = null
139
-
140
- // 格式化数字(带单位)
141
- function formatNumber(num: number): string {
142
- if (num >= 1000000) {
143
- return (num / 1000000).toFixed(1) + 'M'
144
- } else if (num >= 1000) {
145
- return (num / 1000).toFixed(1) + 'K'
146
- }
147
- return num.toString()
148
- }
149
-
150
- // 格式化时间戳(支持数字毫秒或 HH:MM 字符串)
151
- function formatTimestamp(time: string | number): string {
152
- if (typeof time === 'number') {
153
- return new Date(time).toLocaleString('zh-CN', {
154
- hour: '2-digit',
155
- minute: '2-digit',
156
- hour12: false
157
- })
158
- }
159
- // 兼容旧格式 HH:MM(UTC)
160
- const [hours, minutes] = String(time).split(':').map(Number)
161
- const now = new Date()
162
- const utcDate = new Date(Date.UTC(
163
- now.getUTCFullYear(),
164
- now.getUTCMonth(),
165
- now.getUTCDate(),
166
- hours,
167
- minutes,
168
- 0,
169
- 0
170
- ))
171
- return utcDate.toLocaleString('zh-CN', {
172
- timeZone: 'Asia/Shanghai',
173
- hour: '2-digit',
174
- minute: '2-digit',
175
- hour12: false
176
- })
177
- }
178
-
179
- // 计算柱子高度百分比
180
- function getBarHeight(value: number, max: number): number {
181
- if (max === 0) return 0
182
- return Math.max((value / max) * 100, 5) // 最小高度 5%
183
- }
184
-
185
- // 更新当前日期时间
186
- function updateDateTime() {
187
- const now = new Date()
188
- currentDateTime.value = now.toLocaleString('zh-CN', {
189
- year: 'numeric',
190
- month: 'long',
191
- day: 'numeric',
192
- hour: '2-digit',
193
- minute: '2-digit',
194
- hour12: false
195
- })
196
- }
197
-
198
-
199
-
200
- async function updateMetrics() {
201
- // 获取连接数
202
- try {
203
- const res = await fetch('/api/websocket/connections')
204
- const data = await res.json()
205
- connectionsCount.value = data.count || 0
206
- } catch (e) {
207
- console.error('获取连接数失败:', e)
208
- }
209
-
210
- // 测量 API 响应时间
211
- const startTime = Date.now()
212
- try {
213
- await fetch('/api/performance')
214
- apiLatency.value = Date.now() - startTime
215
- } catch (e) {
216
- console.error('测量 API 响应时间失败:', e)
217
- }
218
-
219
- // 获取真实性能数据(固定为 20 分钟)
220
- try {
221
- const res = await fetch('/api/performance?range=20m')
222
- const data = await res.json()
223
-
224
- // 当前值
225
- currentTpm.value = data.current?.tpm || 0
226
- currentRpm.value = data.current?.rpm || 0
227
-
228
- // 历史数据
229
- tpmHistory.value = data.history?.tpm || []
230
- rpmHistory.value = data.history?.rpm || []
231
- timestamps.value = data.history?.timestamps || []
232
-
233
- // 总计
234
- totalTokens.value = data.total?.tokens || 0
235
- totalRequests.value = data.total?.requests || 0
236
-
237
- // 更新最大值
238
- if (tpmHistory.value.length > 0) {
239
- maxTpm.value = Math.max(...tpmHistory.value, 100)
240
- }
241
- if (rpmHistory.value.length > 0) {
242
- maxRpm.value = Math.max(...rpmHistory.value, 10)
243
- }
244
- } catch (e) {
245
- console.error('获取性能数据失败:', e)
246
- }
247
- }
248
-
249
- onMounted(() => {
250
- // 初始化
251
- updateDateTime()
252
- updateMetrics()
253
-
254
- // 每 10 秒更新一次指标
255
- monitorInterval = setInterval(() => {
256
- updateMetrics()
257
- }, 10000)
258
-
259
- // 每秒更新时间
260
- dateTimeInterval = setInterval(updateDateTime, 1000)
261
- })
262
-
263
- onUnmounted(() => {
264
- if (monitorInterval) {
265
- clearInterval(monitorInterval)
266
- }
267
- if (dateTimeInterval) {
268
- clearInterval(dateTimeInterval)
269
- }
270
- })
271
- </script>
272
-
273
- <style scoped>
274
- .performance-monitor {
275
- padding: 24px;
276
- background: #ffffff;
277
- border-radius: 12px;
278
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
279
- max-width: 1600px;
280
- margin: 0 auto;
281
- }
282
-
283
- .header-row {
284
- display: flex;
285
- justify-content: space-between;
286
- align-items: center;
287
- margin-bottom: 1.5rem;
288
- }
289
-
290
- h2 {
291
- margin: 0;
292
- font-size: 1.3rem;
293
- font-weight: 600;
294
- color: #111827;
295
- }
296
-
297
- .metrics-grid {
298
- display: grid;
299
- grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
300
- gap: 1rem;
301
- margin-bottom: 2rem;
302
- }
303
-
304
- .metric-card {
305
- padding: 1.5rem;
306
- background: #f9fafb;
307
- border-radius: 8px;
308
- border: 1px solid #e5e7eb;
309
- text-align: center;
310
- }
311
-
312
- .metric-label {
313
- font-size: 0.85rem;
314
- color: #6b7280;
315
- margin-bottom: 0.5rem;
316
- }
317
-
318
- .metric-value {
319
- font-size: 2rem;
320
- font-weight: 600;
321
- color: #111827;
322
- margin-bottom: 0.25rem;
323
- }
324
-
325
- .metric-unit {
326
- font-size: 0.75rem;
327
- color: #9ca3af;
328
- margin-bottom: 0.25rem;
329
- }
330
-
331
- .metric-desc {
332
- font-size: 0.7rem;
333
- color: #9ca3af;
334
- margin-top: 0.5rem;
335
- }
336
-
337
- /* 图表容器 - 上下排列 */
338
- .charts-container {
339
- display: flex;
340
- flex-direction: column;
341
- gap: 2rem;
342
- margin-bottom: 2rem;
343
- }
344
-
345
- .chart-card {
346
- background: #ffffff;
347
- border-radius: 12px;
348
- border: 1px solid #e5e7eb;
349
- padding: 20px;
350
- width: 100%;
351
- }
352
-
353
- .chart-header {
354
- display: flex;
355
- justify-content: space-between;
356
- align-items: center;
357
- margin-bottom: 16px;
358
- }
359
-
360
- .chart-header h3 {
361
- margin: 0;
362
- font-size: 1.125rem;
363
- font-weight: 600;
364
- color: #111827;
365
- display: flex;
366
- align-items: center;
367
- gap: 8px;
368
- }
369
-
370
- .chart-datetime {
371
- font-size: 0.9rem;
372
- color: #6b7280;
373
- font-weight: 500;
374
- }
375
-
376
- .chart-wrapper {
377
- background: #f9fafb;
378
- border-radius: 8px;
379
- padding: 24px 20px;
380
- position: relative;
381
- }
382
-
383
- .chart-bars {
384
- display: flex;
385
- align-items: flex-end;
386
- justify-content: space-between;
387
- gap: 12px; /* 柱子间距 */
388
- height: 280px; /* 图表高度 - 增加,给数字标签更多空间 */
389
- padding-bottom: 40px; /* 为时间标签预留空间 */
390
- position: relative;
391
- padding-top: 30px; /* 为数字标签预留顶部空间 */
392
- }
393
-
394
- .chart-bar {
395
- flex: 1;
396
- min-width: 35px; /* 最小宽度,确保数字可读 */
397
- max-width: 60px; /* 最大宽度,避免过宽 */
398
- display: flex;
399
- flex-direction: column;
400
- align-items: center;
401
- position: relative;
402
- }
403
-
404
- /* 数字标签 - 始终显示在柱子上方 */
405
- .bar-value {
406
- position: absolute;
407
- bottom: calc(100% + 8px); /* 柱子顶部上方 8px */
408
- left: 50%;
409
- transform: translateX(-50%);
410
- font-size: 0.85rem; /* 13.6px */
411
- font-weight: 600;
412
- color: #374151;
413
- line-height: 1.2;
414
- white-space: nowrap;
415
- z-index: 10;
416
- }
417
-
418
- /* 柱子本体 */
419
- .bar-visual {
420
- width: 100%;
421
- min-height: 4px; /* 最小高度,确保零值也可见 */
422
- border-radius: 6px 6px 0 0;
423
- position: relative;
424
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
425
- }
426
-
427
- .bar-visual:hover {
428
- filter: brightness(1.1);
429
- transform: scaleY(1.02);
430
- transform-origin: bottom;
431
- }
432
-
433
- /* TPM 图表柱子颜色 */
434
- .chart-card[data-type="tpm"] .bar-visual {
435
- background: linear-gradient(to top, #3b82f6, #60a5fa);
436
- }
437
-
438
- /* RPM 图表柱子颜色 */
439
- .chart-card[data-type="rpm"] .bar-visual {
440
- background: linear-gradient(to top, #10b981, #34d399);
441
- }
442
-
443
- /* 零值柱子 */
444
- .chart-bar[data-value="0"] .bar-visual {
445
- background: #e5e7eb !important;
446
- }
447
-
448
- /* 时间标签 */
449
- .bar-time-label {
450
- position: absolute;
451
- bottom: -30px; /* 距离柱子底部 30px */
452
- left: 50%;
453
- transform: translateX(-50%);
454
- font-size: 0.75rem; /* 12px */
455
- color: #6b7280; /* 稍微深一点,更清晰 */
456
- font-weight: 500;
457
- white-space: nowrap;
458
- }
459
-
460
- .stats-summary {
461
- padding-top: 1.5rem;
462
- border-top: 1px solid #e5e7eb;
463
- }
464
-
465
- .stats-summary h3 {
466
- margin: 0 0 1rem 0;
467
- font-size: 1rem;
468
- font-weight: 600;
469
- color: #111827;
470
- }
471
-
472
- .summary-grid {
473
- display: grid;
474
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
475
- gap: 1rem;
476
- }
477
-
478
- .summary-item {
479
- padding: 1rem;
480
- background: #f9fafb;
481
- border-radius: 6px;
482
- display: flex;
483
- align-items: center;
484
- gap: 0.75rem;
485
- }
486
-
487
- .summary-label {
488
- font-size: 0.85rem;
489
- color: #6b7280;
490
- }
491
-
492
- .summary-value {
493
- font-size: 1.5rem;
494
- font-weight: 600;
495
- color: #111827;
496
- }
497
-
498
- .summary-unit {
499
- font-size: 0.75rem;
500
- color: #9ca3af;
501
- }
502
-
503
- /* 响应式设计 */
504
- @media (max-width: 768px) {
505
- .chart-bars {
506
- overflow-x: auto;
507
- -webkit-overflow-scrolling: touch;
508
- }
509
-
510
- .chart-bar {
511
- min-width: 40px;
512
- flex: 0 0 40px;
513
- }
514
- }
515
- </style>