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

查看文件

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

查看文件

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

查看文件

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

查看文件

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

查看文件

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

查看文件

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