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>
这个提交包含在:
父节点
b83df44444
当前提交
c8224bf598
@ -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>
|
||||
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户