fix(bugcollect): 修复 undefined.length 崩溃 — 所有视图加 gateStatus 守卫和 null safety

- 所有视图 loadData/loadWebhooks:首行加 gateStatus !== 'enabled' 守卫,避免 appKey 为空或服务未开通时发请求
- 所有视图加 watch(gateStatus) 监听,服务开通后自动刷新数据
- 所有响应体数组赋值改为 ?? [],防止后端返回 null/undefined 时 el-table 访问 .length 崩溃
- BugCollectOverview:maxRate computed 改用 crashRateTrend ?? [] 防止 undefined.map 崩溃

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-06-17 10:07:32 +08:00
父节点 b83df44444
当前提交 c8224bf598
共有 7 个文件被更改,包括 58 次插入26 次删除

查看文件

@ -101,7 +101,7 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, watch } from 'vue'
import { bugCollectApi, type BugCollectEventItem } from '@/api/bugcollect'
import { useBugCollectApp } from '@/composables/useBugCollectApp'
@ -125,6 +125,7 @@ function formatTime(ts: string) {
}
async function loadData() {
if (!appKey.value || gateStatus.value !== 'enabled') return
loading.value = true
try {
const res = await bugCollectApi.events(appKey.value, {
@ -136,8 +137,8 @@ async function loadData() {
size: pageSize.value,
})
const data = res.data.data
events.value = data.content
total.value = data.totalElements
events.value = data.content ?? []
total.value = data.totalElements ?? 0
} catch {
} finally {
loading.value = false
@ -155,6 +156,7 @@ function handleSizeChange(size: number) {
loadData()
}
watch(gateStatus, (s) => { if (s === 'enabled') loadData() })
onMounted(loadData)
</script>

查看文件

@ -96,12 +96,13 @@ function funnelColor(idx: number) {
async function analyze() {
const validSteps = steps.value.filter((s) => s.trim())
if (validSteps.length < 2) return
if (validSteps.length < 2 || !appKey.value) return
loading.value = true
try {
const res = await bugCollectApi.funnel(appKey.value, validSteps)
funnelData.value = res.data.data
funnelData.value = res.data.data ?? []
} catch {
funnelData.value = []
} finally {
loading.value = false
}

查看文件

@ -100,7 +100,7 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, watch } from 'vue'
import { bugCollectApi, type BugCollectIssue } from '@/api/bugcollect'
import { useBugCollectApp } from '@/composables/useBugCollectApp'
@ -134,6 +134,7 @@ function formatTime(ts: string) {
}
async function loadData() {
if (!appKey.value || gateStatus.value !== 'enabled') return
loading.value = true
try {
const res = await bugCollectApi.issues(appKey.value, {
@ -145,8 +146,8 @@ async function loadData() {
size: pageSize.value,
})
const data = res.data.data
issues.value = data.content
total.value = data.totalElements
issues.value = data.content ?? []
total.value = data.totalElements ?? 0
} catch {
} finally {
loading.value = false
@ -164,6 +165,7 @@ function handleSizeChange(size: number) {
loadData()
}
watch(gateStatus, (s) => { if (s === 'enabled') loadData() })
onMounted(loadData)
</script>

查看文件

@ -111,24 +111,27 @@
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { ref, onMounted, computed, watch } from 'vue'
import { bugCollectApi, type BugCollectOverview } from '@/api/bugcollect'
import { Warning, Plus, User } from '@element-plus/icons-vue'
import { useBugCollectApp } from '@/composables/useBugCollectApp'
const { apps, loadingApps, appKey, setApp, gateStatus, applyDialogVisible, applyReason, applyLoading, submitActivation } = useBugCollectApp()
const overview = ref<BugCollectOverview>({
const EMPTY: BugCollectOverview = {
totalIssues: 0,
todayNewIssues: 0,
affectedUsers: 0,
crashRate: 0,
crashRateTrend: [],
topIssues: [],
})
}
const overview = ref<BugCollectOverview>({ ...EMPTY })
const maxRate = computed(() => {
const rates = overview.value.crashRateTrend.map((i) => i.rate)
const trend = overview.value.crashRateTrend ?? []
const rates = trend.map((i) => i.rate)
return Math.max(...rates, 1)
})
@ -146,12 +149,26 @@ function issueTypeTag(type: string) {
return (map[type] ?? '') as '' | 'success' | 'warning' | 'info' | 'danger'
}
onMounted(async () => {
async function loadData() {
if (!appKey.value || gateStatus.value !== 'enabled') return
try {
const res = await bugCollectApi.overview(appKey.value)
overview.value = res.data.data
} catch {}
})
const d = res.data.data ?? {}
overview.value = {
totalIssues: d.totalIssues ?? 0,
todayNewIssues: d.todayNewIssues ?? 0,
affectedUsers: d.affectedUsers ?? 0,
crashRate: d.crashRate ?? 0,
crashRateTrend: d.crashRateTrend ?? [],
topIssues: d.topIssues ?? [],
}
} catch {
overview.value = { ...EMPTY }
}
}
watch(gateStatus, (s) => { if (s === 'enabled') loadData() })
onMounted(loadData)
</script>
<style scoped>

查看文件

@ -61,7 +61,7 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, watch } from 'vue'
import { bugCollectApi, type BugCollectIssueRanking } from '@/api/bugcollect'
import { useBugCollectApp } from '@/composables/useBugCollectApp'
@ -85,16 +85,20 @@ function formatTime(ts: string) {
return new Date(ts).toLocaleString('zh-CN')
}
onMounted(async () => {
async function loadData() {
if (!appKey.value || gateStatus.value !== 'enabled') return
loading.value = true
try {
const res = await bugCollectApi.frequencyRanking(appKey.value)
rankings.value = res.data.data
rankings.value = res.data.data ?? []
} catch {
rankings.value = []
} finally {
loading.value = false
}
})
}
watch(gateStatus, (s) => { if (s === 'enabled') loadData() })
onMounted(loadData)
</script>
<style scoped>

查看文件

@ -66,7 +66,7 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, watch } from 'vue'
import { bugCollectApi, type BugCollectIssueRanking } from '@/api/bugcollect'
import { useBugCollectApp } from '@/composables/useBugCollectApp'
@ -97,16 +97,20 @@ function formatTime(ts: string) {
return new Date(ts).toLocaleString('zh-CN')
}
onMounted(async () => {
async function loadData() {
if (!appKey.value || gateStatus.value !== 'enabled') return
loading.value = true
try {
const res = await bugCollectApi.riskRanking(appKey.value)
rankings.value = res.data.data
rankings.value = res.data.data ?? []
} catch {
rankings.value = []
} finally {
loading.value = false
}
})
}
watch(gateStatus, (s) => { if (s === 'enabled') loadData() })
onMounted(loadData)
</script>
<style scoped>

查看文件

@ -100,7 +100,7 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { bugCollectApi, type BugCollectWebhook } from '@/api/bugcollect'
import { useBugCollectApp } from '@/composables/useBugCollectApp'
@ -121,10 +121,11 @@ const form = ref({
})
async function loadWebhooks() {
if (!appKey.value || gateStatus.value !== 'enabled') return
loading.value = true
try {
const res = await bugCollectApi.webhooks.list(appKey.value)
webhooks.value = res.data.data
webhooks.value = res.data.data ?? []
} catch {
} finally {
loading.value = false
@ -174,6 +175,7 @@ async function handleDelete(id: string) {
} catch {}
}
watch(gateStatus, (s) => { if (s === 'enabled') loadWebhooks() })
onMounted(loadWebhooks)
</script>