131 行
4.6 KiB
Vue
131 行
4.6 KiB
Vue
<template>
|
|
<div>
|
|
<el-menu mode="horizontal" :default-active="$route.path" router class="log-nav" style="margin-bottom: 16px">
|
|
<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>
|
|
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px">
|
|
<h2 style="margin: 0">错误列表</h2>
|
|
<el-button @click="loadIssues" :loading="loading">刷新</el-button>
|
|
</div>
|
|
|
|
<el-card style="margin-bottom: 16px">
|
|
<el-form :inline="true" :model="filters" size="default">
|
|
<el-form-item label="类型">
|
|
<el-select v-model="filters.type" clearable placeholder="全部" style="width: 140px">
|
|
<el-option label="Crash" value="crash" />
|
|
<el-option label="ANR" value="anr" />
|
|
<el-option label="JS Error" value="js_error" />
|
|
<el-option label="Native Error" value="native_error" />
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="平台">
|
|
<el-select v-model="filters.platform" clearable placeholder="全部" style="width: 120px">
|
|
<el-option label="Android" value="android" />
|
|
<el-option label="iOS" value="ios" />
|
|
<el-option label="HarmonyOS" value="harmony" />
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="日期">
|
|
<el-date-picker
|
|
v-model="filters.dateRange"
|
|
type="daterange"
|
|
range-separator="至"
|
|
start-placeholder="开始"
|
|
end-placeholder="结束"
|
|
value-format="YYYY-MM-DD"
|
|
style="width: 260px"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item>
|
|
<el-button type="primary" @click="loadIssues">查询</el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
</el-card>
|
|
|
|
<el-table :data="issues" v-loading="loading" border stripe>
|
|
<el-table-column prop="title" label="标题" min-width="260" show-overflow-tooltip>
|
|
<template #default="{ row }">
|
|
<el-button link type="primary" @click="$router.push(`/log/issues/${row.id}`)">
|
|
{{ row.title }}
|
|
</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="type" label="类型" width="120">
|
|
<template #default="{ row }">
|
|
<el-tag size="small" :type="typeTag(row.type)">{{ row.type }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="count" label="次数" width="100" align="right" sortable />
|
|
<el-table-column prop="affectedUsers" label="影响用户" width="110" align="right" sortable />
|
|
<el-table-column prop="platform" label="平台" width="100" />
|
|
<el-table-column prop="lastSeenAt" label="最后出现" width="170">
|
|
<template #default="{ row }">{{ formatTime(row.lastSeenAt) }}</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<el-pagination
|
|
style="margin-top: 16px"
|
|
v-model:current-page="page"
|
|
:page-size="size"
|
|
:total="total"
|
|
layout="total, prev, pager, next"
|
|
@current-change="loadIssues"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
import { logApi, type LogIssueSummary } from '@/api/log'
|
|
import { formatTime } from '@/utils/date'
|
|
|
|
const issues = ref<LogIssueSummary[]>([])
|
|
const loading = ref(false)
|
|
const page = ref(1)
|
|
const size = ref(20)
|
|
const total = ref(0)
|
|
|
|
const filters = reactive({
|
|
type: '',
|
|
platform: '',
|
|
dateRange: null as [string, string] | null,
|
|
})
|
|
|
|
function typeTag(t: string): '' | 'success' | 'warning' | 'info' | 'danger' {
|
|
if (t === 'crash') return 'danger'
|
|
if (t === 'anr') return 'warning'
|
|
if (t === 'js_error') return ''
|
|
return 'info'
|
|
}
|
|
|
|
async function loadIssues() {
|
|
loading.value = true
|
|
try {
|
|
const res = await logApi.listIssues({
|
|
type: filters.type || undefined,
|
|
platform: filters.platform || undefined,
|
|
startDate: filters.dateRange?.[0] || undefined,
|
|
endDate: filters.dateRange?.[1] || undefined,
|
|
page: page.value - 1,
|
|
size: size.value,
|
|
})
|
|
const data = res.data.data
|
|
issues.value = data.content ?? []
|
|
total.value = data.total ?? 0
|
|
} catch {
|
|
issues.value = []
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(loadIssues)
|
|
</script>
|