Files
test-repo/docs/.vitepress/theme/components/appendix/operations/MonitoringDashboardDemo.vue
T

447 lines
9.4 KiB
Vue
Raw Normal View History

<!--
MonitoringDashboardDemo.vue
监控面板演示展示基础设施应用业务三个层次的监控指标
-->
<template>
<div class="monitoring-dashboard">
<div class="header">
<div class="title">实时监控面板 (Monitoring Dashboard)</div>
<div class="subtitle">运维的"眼睛" - 让系统状态一目了然</div>
</div>
<div class="tabs">
<button
v-for="tab in tabs"
:key="tab.id"
:class="['tab', { active: activeTab === tab.id }]"
@click="activeTab = tab.id"
>
{{ tab.name }}
</button>
</div>
<div class="dashboard-content">
<!-- 基础设施监控 -->
<div v-if="activeTab === 'infra'" class="metrics-grid">
<div
v-for="metric in infraMetrics"
:key="metric.name"
class="metric-card"
>
<div class="metric-header">
<span class="metric-name">{{ metric.name }}</span>
<span class="metric-value"
>{{ metric.value }}{{ metric.unit }}</span
>
</div>
<div class="metric-chart">
<div
class="chart-bar"
:style="{
width: metric.value + '%',
background: getColor(metric.value, metric.threshold)
}"
></div>
</div>
<div
class="metric-status"
:class="getStatus(metric.value, metric.threshold)"
>
{{ getStatusText(metric.value, metric.threshold) }}
</div>
</div>
</div>
<!-- 应用监控 -->
<div v-if="activeTab === 'app'" class="metrics-grid">
<div class="metric-card large">
<div class="metric-header">
<span class="metric-name">QPS (每秒请求数)</span>
<span class="metric-value">{{ qps }}</span>
</div>
<div class="qps-chart">
<div
v-for="(height, index) in qpsHistory"
:key="index"
class="qps-bar"
:style="{ height: height + '%' }"
></div>
</div>
</div>
<div class="metric-card">
<div class="metric-header">
<span class="metric-name">平均响应时间</span>
<span class="metric-value">{{ latency }} ms</span>
</div>
<div
class="metric-status"
:class="latency > 500 ? 'critical' : 'normal'"
>
{{ latency > 500 ? '需要优化' : '正常' }}
</div>
</div>
<div class="metric-card">
<div class="metric-header">
<span class="metric-name">错误率</span>
<span class="metric-value">{{ errorRate }}%</span>
</div>
<div
class="metric-status"
:class="errorRate > 1 ? 'critical' : 'normal'"
>
{{ errorRate > 1 ? '告警' : '正常' }}
</div>
</div>
</div>
<!-- 业务监控 -->
<div v-if="activeTab === 'business'" class="metrics-grid">
<div
v-for="metric in businessMetrics"
:key="metric.name"
class="metric-card"
>
<div class="metric-header">
<span class="metric-name">{{ metric.name }}</span>
<span class="metric-value">{{ metric.value }}</span>
</div>
<div class="trend" :class="metric.trend">
{{
metric.trend === 'up'
? '📈 上升'
: metric.trend === 'down'
? '📉 下降'
: '➡️ 持平'
}}
</div>
<div class="metric-desc">{{ metric.desc }}</div>
</div>
</div>
</div>
<div class="legend">
<div class="item">
<span class="dot normal"></span>
<span>正常 (Normal)</span>
</div>
<div class="item">
<span class="dot warning"></span>
<span>警告 (Warning)</span>
</div>
<div class="item">
<span class="dot critical"></span>
<span>严重 (Critical)</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const activeTab = ref('infra')
const tabs = [
{ id: 'infra', name: '基础设施' },
{ id: 'app', name: '应用监控' },
{ id: 'business', name: '业务监控' }
]
const infraMetrics = ref([
{ name: 'CPU 使用率', value: 45, unit: '%', threshold: 80 },
{ name: '内存使用率', value: 62, unit: '%', threshold: 85 },
{ name: '磁盘使用率', value: 78, unit: '%', threshold: 90 },
{ name: '网络带宽', value: 34, unit: '%', threshold: 80 },
{ name: '磁盘 I/O', value: 55, unit: '%', threshold: 70 },
{ name: '负载均衡', value: 42, unit: '%', threshold: 75 }
])
const qps = ref(1250)
const latency = ref(180)
const errorRate = ref(0.12)
const qpsHistory = ref([
40, 55, 45, 60, 50, 65, 70, 60, 75, 80, 70, 85, 90, 80, 95, 100
])
const businessMetrics = ref([
{
name: '在线用户数',
value: '12,458',
trend: 'up',
desc: '当前实时在线用户'
},
{
name: '订单量/小时',
value: '856',
trend: 'up',
desc: '过去一小时的订单数'
},
{
name: '支付成功率',
value: '98.5%',
trend: 'stable',
desc: '支付成功的比例'
},
{
name: 'DAU (日活)',
value: '45,621',
trend: 'up',
desc: '今日活跃用户数'
}
])
let interval = null
const getColor = (value, threshold) => {
if (value >= threshold) return '#ef4444'
if (value >= threshold * 0.8) return '#f59e0b'
return '#22c55e'
}
const getStatus = (value, threshold) => {
if (value >= threshold) return 'critical'
if (value >= threshold * 0.8) return 'warning'
return 'normal'
}
const getStatusText = (value, threshold) => {
if (value >= threshold) return '严重'
if (value >= threshold * 0.8) return '警告'
return '正常'
}
const updateMetrics = () => {
// 更新基础设施指标
infraMetrics.value = infraMetrics.value.map((metric) => ({
...metric,
value: Math.max(0, Math.min(100, metric.value + (Math.random() - 0.5) * 10))
}))
// 更新应用指标
qps.value = Math.round(1200 + Math.random() * 200)
latency.value = Math.round(150 + Math.random() * 100)
errorRate.value = Math.max(
0,
Math.round((0.1 + Math.random() * 0.3) * 100) / 100
)
// 更新 QPS 历史图表
qpsHistory.value.shift()
qpsHistory.value.push(Math.round(40 + Math.random() * 60))
}
onMounted(() => {
interval = setInterval(updateMetrics, 2000)
})
onUnmounted(() => {
if (interval) clearInterval(interval)
})
</script>
<style scoped>
.monitoring-dashboard {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 12px;
padding: 1.5rem;
margin: 1.5rem 0;
font-family: var(--vp-font-family-base);
}
.header {
margin-bottom: 1.5rem;
}
.title {
font-weight: 700;
font-size: 1.1rem;
margin-bottom: 0.25rem;
}
.subtitle {
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
border-bottom: 1px solid var(--vp-c-divider);
padding-bottom: 0.5rem;
}
.tab {
padding: 0.5rem 1rem;
border: none;
background: none;
cursor: pointer;
font-size: 0.9rem;
color: var(--vp-c-text-2);
border-radius: 6px;
transition: all 0.2s;
}
.tab:hover {
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
}
.tab.active {
background: var(--vp-c-brand);
color: #fff;
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
.metric-card {
background: var(--vp-c-bg);
border-radius: 10px;
padding: 1rem;
border: 1px solid var(--vp-c-divider);
}
.metric-card.large {
grid-column: span 2;
}
.metric-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.metric-name {
font-size: 0.9rem;
font-weight: 600;
color: var(--vp-c-text-1);
}
.metric-value {
font-size: 1.2rem;
font-weight: 700;
color: var(--vp-c-brand);
}
.metric-chart {
height: 8px;
background: var(--vp-c-bg-soft);
border-radius: 999px;
overflow: hidden;
margin-bottom: 0.5rem;
}
.chart-bar {
height: 100%;
transition: width 0.5s ease;
}
.metric-status {
font-size: 0.85rem;
font-weight: 600;
}
.metric-status.normal {
color: #22c55e;
}
.metric-status.warning {
color: #f59e0b;
}
.metric-status.critical {
color: #ef4444;
}
.qps-chart {
display: flex;
align-items: flex-end;
gap: 2px;
height: 80px;
margin-top: 0.5rem;
}
.qps-bar {
flex: 1;
background: var(--vp-c-brand);
border-radius: 2px 2px 0 0;
min-height: 10px;
transition: height 0.3s ease;
}
.trend {
font-size: 0.85rem;
margin: 0.5rem 0;
font-weight: 600;
}
.trend.up {
color: #22c55e;
}
.trend.down {
color: #ef4444;
}
.trend.stable {
color: var(--vp-c-text-2);
}
.metric-desc {
font-size: 0.8rem;
color: var(--vp-c-text-2);
}
.legend {
display: flex;
gap: 1.5rem;
margin-top: 1.5rem;
padding-top: 1rem;
border-top: 1px solid var(--vp-c-divider);
font-size: 0.85rem;
}
.item {
display: flex;
align-items: center;
gap: 0.5rem;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.dot.normal {
background: #22c55e;
}
.dot.warning {
background: #f59e0b;
}
.dot.critical {
background: #ef4444;
}
@media (max-width: 768px) {
.metrics-grid {
grid-template-columns: 1fr;
}
.metric-card.large {
grid-column: span 1;
}
}
</style>