Files
test-repo/docs/.vitepress/theme/components/appendix/operations/MonitoringDashboardDemo.vue
T
sanbuphy 0eba9e87e9 fix(eslint): reduce warnings in GitHub Actions deployment
- Disable formatting rules (handled by Prettier)
- Relaxed strict Vue/JS rules for demo code compatibility
- Fix syntax errors in ApiPlayground and VoiceCloningDemo
- Fix duplicate else-if condition in ApiPlayground
- Fix Promise executor async pattern in AutoregressiveAudioDemo
- Add TypeScript file support to ESLint config

Warnings reduced from 295 to 251 problems.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-18 17:38:10 +08:00

463 lines
9.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
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
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 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>正常 (Normal)</span>
</div>
<div class="item">
<span class="dot warning" />
<span>警告 (Warning)</span>
</div>
<div class="item">
<span class="dot critical" />
<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: 0.75rem;
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>