129 行
4.1 KiB
Vue
129 行
4.1 KiB
Vue
|
|
<template>
|
||
|
|
<div>
|
||
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px">
|
||
|
|
<h2 style="margin: 0">日志监控概览</h2>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<el-menu mode="horizontal" :default-active="$route.path" router class="log-nav">
|
||
|
|
<el-menu-item index="/log/overview">概览</el-menu-item>
|
||
|
|
<el-menu-item index="/log/issues">错误列表</el-menu-item>
|
||
|
|
<el-menu-item index="/log/events">事件流水</el-menu-item>
|
||
|
|
<el-menu-item index="/log/funnels">漏斗分析</el-menu-item>
|
||
|
|
<el-menu-item index="/log/rank/freq">高频排行</el-menu-item>
|
||
|
|
<el-menu-item index="/log/rank/risk">高危排行</el-menu-item>
|
||
|
|
<el-menu-item index="/log/webhooks">Webhook</el-menu-item>
|
||
|
|
</el-menu>
|
||
|
|
|
||
|
|
<p style="margin: 16px 0 24px; color: #606266">
|
||
|
|
汇总错误追踪、崩溃率与影响用户,快速判断应用健康状态。
|
||
|
|
</p>
|
||
|
|
|
||
|
|
<el-row :gutter="16">
|
||
|
|
<el-col :xs="12" :sm="6" v-for="item in stats" :key="item.label">
|
||
|
|
<el-card shadow="hover">
|
||
|
|
<el-statistic :title="item.label" :value="item.value" />
|
||
|
|
</el-card>
|
||
|
|
</el-col>
|
||
|
|
</el-row>
|
||
|
|
|
||
|
|
<el-row :gutter="16" style="margin-top: 16px">
|
||
|
|
<el-col :xs="24" :md="14">
|
||
|
|
<el-card>
|
||
|
|
<template #header>近 7 天崩溃率趋势</template>
|
||
|
|
<div ref="trendChartRef" class="chart-box" />
|
||
|
|
</el-card>
|
||
|
|
</el-col>
|
||
|
|
<el-col :xs="24" :md="10">
|
||
|
|
<el-card>
|
||
|
|
<template #header>Top 5 高频错误</template>
|
||
|
|
<el-table :data="topIssues" size="small" stripe>
|
||
|
|
<el-table-column prop="title" label="标题" min-width="200" show-overflow-tooltip />
|
||
|
|
<el-table-column prop="count" label="次数" width="80" align="right" />
|
||
|
|
<el-table-column prop="platform" label="平台" width="80" />
|
||
|
|
</el-table>
|
||
|
|
</el-card>
|
||
|
|
</el-col>
|
||
|
|
</el-row>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup lang="ts">
|
||
|
|
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||
|
|
import { logApi, type LogIssueSummary } from '@/api/log'
|
||
|
|
import * as echarts from 'echarts'
|
||
|
|
|
||
|
|
const stats = ref([
|
||
|
|
{ label: '总 Issue 数', value: 0 },
|
||
|
|
{ label: '今日新增', value: 0 },
|
||
|
|
{ label: '影响用户数', value: 0 },
|
||
|
|
{ label: '崩溃率', value: '0%' },
|
||
|
|
])
|
||
|
|
|
||
|
|
const topIssues = ref<LogIssueSummary[]>([])
|
||
|
|
const trendChartRef = ref<HTMLDivElement>()
|
||
|
|
let trendChart: echarts.ECharts | null = null
|
||
|
|
|
||
|
|
function initTrendChart(dailyTrend: Array<{ date: string; crashRate: number }>) {
|
||
|
|
if (!trendChartRef.value) return
|
||
|
|
const dates = dailyTrend.map((d) => {
|
||
|
|
const parts = d.date.split('-')
|
||
|
|
return `${parseInt(parts[1])}/${parseInt(parts[2])}`
|
||
|
|
})
|
||
|
|
const values = dailyTrend.map((d) => d.crashRate)
|
||
|
|
trendChart = echarts.init(trendChartRef.value)
|
||
|
|
trendChart.setOption({
|
||
|
|
tooltip: { trigger: 'axis', formatter: '{b}<br/>崩溃率: {c}%' },
|
||
|
|
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||
|
|
xAxis: { type: 'category', data: dates, boundaryGap: false },
|
||
|
|
yAxis: { type: 'value', axisLabel: { formatter: '{value}%' } },
|
||
|
|
series: [
|
||
|
|
{
|
||
|
|
name: '崩溃率',
|
||
|
|
type: 'line',
|
||
|
|
smooth: true,
|
||
|
|
data: values,
|
||
|
|
areaStyle: {
|
||
|
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||
|
|
{ offset: 0, color: 'rgba(245,108,108,0.3)' },
|
||
|
|
{ offset: 1, color: 'rgba(245,108,108,0.05)' },
|
||
|
|
]),
|
||
|
|
},
|
||
|
|
itemStyle: { color: '#f56c6c' },
|
||
|
|
},
|
||
|
|
],
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
function handleResize() {
|
||
|
|
trendChart?.resize()
|
||
|
|
}
|
||
|
|
|
||
|
|
onMounted(async () => {
|
||
|
|
try {
|
||
|
|
const res = await logApi.getOverview()
|
||
|
|
const d = res.data.data
|
||
|
|
stats.value[0].value = d.totalIssues ?? 0
|
||
|
|
stats.value[1].value = d.todayNew ?? 0
|
||
|
|
stats.value[2].value = d.affectedUsers ?? 0
|
||
|
|
stats.value[3].value = `${((d.crashRate ?? 0) * 100).toFixed(2)}%`
|
||
|
|
topIssues.value = (d.topIssues ?? []).slice(0, 5)
|
||
|
|
initTrendChart(d.dailyTrend ?? [])
|
||
|
|
} catch {
|
||
|
|
initTrendChart([])
|
||
|
|
}
|
||
|
|
window.addEventListener('resize', handleResize)
|
||
|
|
})
|
||
|
|
|
||
|
|
onBeforeUnmount(() => {
|
||
|
|
window.removeEventListener('resize', handleResize)
|
||
|
|
trendChart?.dispose()
|
||
|
|
})
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped>
|
||
|
|
.chart-box {
|
||
|
|
width: 100%;
|
||
|
|
height: 320px;
|
||
|
|
}
|
||
|
|
</style>
|