Files
xinghuoapi/frontend/src/views/admin/ops/components/OpsLatencyChart.vue
IanShaw027 8ae75e7f6e feat(前端UI): 实现运维监控前端界面
- 新增帮助提示组件(HelpTooltip.vue)
- 更新侧边栏添加 ops 监控菜单项
- 扩展设置视图集成 ops 配置面板
- 新增 ops 监控视图目录(dashboard, alerts, realtime, settings 等)
2026-01-09 21:00:04 +08:00

102 lines
3.2 KiB
Vue

<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { Chart as ChartJS, BarElement, CategoryScale, Legend, LinearScale, Tooltip } from 'chart.js'
import { Bar } from 'vue-chartjs'
import type { OpsLatencyHistogramResponse } from '@/api/admin/ops'
import type { ChartState } from '../types'
import HelpTooltip from '@/components/common/HelpTooltip.vue'
import EmptyState from '@/components/common/EmptyState.vue'
ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend)
interface Props {
latencyData: OpsLatencyHistogramResponse | null
loading: boolean
}
const props = defineProps<Props>()
const { t } = useI18n()
const isDarkMode = computed(() => document.documentElement.classList.contains('dark'))
const colors = computed(() => ({
blue: '#3b82f6',
grid: isDarkMode.value ? '#374151' : '#f3f4f6',
text: isDarkMode.value ? '#9ca3af' : '#6b7280'
}))
const hasData = computed(() => (props.latencyData?.total_requests ?? 0) > 0)
const state = computed<ChartState>(() => {
if (hasData.value) return 'ready'
if (props.loading) return 'loading'
return 'empty'
})
const chartData = computed(() => {
if (!props.latencyData || !hasData.value) return null
const c = colors.value
return {
labels: props.latencyData.buckets.map((b) => b.range),
datasets: [
{
label: t('admin.ops.requests'),
data: props.latencyData.buckets.map((b) => b.count),
backgroundColor: c.blue,
borderRadius: 4,
barPercentage: 0.6
}
]
}
})
const options = computed(() => {
const c = colors.value
return {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
x: {
grid: { display: false },
ticks: { color: c.text, font: { size: 10 } }
},
y: {
beginAtZero: true,
grid: { color: c.grid, borderDash: [4, 4] },
ticks: { color: c.text, font: { size: 10 } }
}
}
}
})
</script>
<template>
<div class="flex h-full flex-col rounded-3xl bg-white p-6 shadow-sm ring-1 ring-gray-900/5 dark:bg-dark-800 dark:ring-dark-700">
<div class="mb-4 flex items-center justify-between">
<h3 class="flex items-center gap-2 text-sm font-bold text-gray-900 dark:text-white">
<svg class="h-4 w-4 text-purple-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
{{ t('admin.ops.latencyHistogram') }}
<HelpTooltip :content="t('admin.ops.tooltips.latencyHistogram')" />
</h3>
</div>
<div class="min-h-0 flex-1">
<Bar v-if="state === 'ready' && chartData" :data="chartData" :options="options" />
<div v-else class="flex h-full items-center justify-center">
<div v-if="state === 'loading'" class="animate-pulse text-sm text-gray-400">{{ t('common.loading') }}</div>
<EmptyState v-else :title="t('common.noData')" :description="t('admin.ops.charts.emptyRequest')" />
</div>
</div>
</div>
</template>