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>
|
</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>
|
||||||
|
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户