fix(bugcollect): 对齐前后端接口契约

- bugcollect.ts: issues/events 请求参数重映射(startDate/endDate→from/to,eventName→name,page 0-based+1→backend 1-based)
- BugCollectPageResult: content/totalElements → items/total(匹配后端 PageResult<T>)
- BugCollectIssue: 字段对齐 IssueResponse(id:number,isResolved,移除 affectedUsers/status)
- BugCollectEventItem: 字段对齐 IssueEventResponse(message/stack/createdAt,移除 eventName/timestamp/properties)
- BugCollectIssueRanking: 同步 isResolved,移除 affectedUsers
- 所有视图表格列更新为实际存在的字段

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-06-17 10:21:00 +08:00
父节点 9cc6a53256
当前提交 3676241f6a
共有 5 个文件被更改,包括 69 次插入49 次删除

查看文件

@ -11,53 +11,65 @@ export interface BugCollectOverview {
topIssues: { id: string; title: string; type: string; count: number }[]
}
// Matches backend IssueResponse
export interface BugCollectIssue {
id: string
title: string
id: number
appKey: string
fingerprint?: string
type: string
platform: string
count: number
affectedUsers: number
lastSeenAt: string
title: string
firstSeenAt: string
status: string
lastSeenAt: string
count: number
isResolved: boolean
platform: string
appVersion?: string
}
// Matches backend IssueResponse (with events embedded)
export interface BugCollectIssueDetail {
id: string
title: string
id: number
appKey: string
fingerprint?: string
type: string
platform: string
appVersion: string
osVersion: string
deviceModel: string
count: number
affectedUsers: number
title: string
firstSeenAt: string
lastSeenAt: string
status: string
stackTrace: string
sourceContext?: { line: number; content: string; highlight: boolean }[]
recentEvents: BugCollectEventItem[]
count: number
isResolved: boolean
platform: string
appVersion?: string
events?: BugCollectEventItem[]
}
// Matches backend IssueEventResponse
export interface BugCollectEventItem {
id: string
eventName: string
userId: string
timestamp: string
properties: Record<string, unknown>
id: number
issueId?: number
appKey?: string
userId?: string
sessionId?: string
message?: string
stack?: string
stackSymbolicated?: string
metadata?: string
platform?: string
appVersion?: string
createdAt: string
}
// Matches backend IssueResponse used for rankings
export interface BugCollectIssueRanking {
id: string
id: number
title: string
type: string
platform: string
count: number
affectedUsers: number
riskScore?: number
isResolved: boolean
firstSeenAt: string
lastSeenAt: string
appVersion?: string
riskScore?: number
}
export interface BugCollectFunnelStep {
@ -76,12 +88,12 @@ export interface BugCollectWebhook {
updatedAt: string
}
// Matches backend PageResult<T> (items/total, not content/totalElements)
export interface BugCollectPageResult<T> {
content: T[]
items: T[]
page: number
size: number
totalElements: number
totalPages: number
total: number
}
// ── API ─────────────────────────────────────────────────────────────────────
@ -91,7 +103,7 @@ export const bugCollectApi = {
overview: (appKey: string) =>
client.get<{ data: BugCollectOverview }>('/bugcollect/v1/overview', { params: { appKey } }),
// Issues
// Issues — page is 1-based (backend convention); startDate/endDate mapped to from/to
issues(appKey: string, params: {
type?: string
platform?: string
@ -100,7 +112,10 @@ export const bugCollectApi = {
page?: number
size?: number
}) {
return client.get<{ data: BugCollectPageResult<BugCollectIssue> }>('/bugcollect/v1/issues', { params: { appKey, ...params } })
const { startDate, endDate, page, ...rest } = params
return client.get<{ data: BugCollectPageResult<BugCollectIssue> }>('/bugcollect/v1/issues', {
params: { appKey, ...rest, from: startDate, to: endDate, page: (page ?? 0) + 1 },
})
},
issueDetail(id: string) {
@ -116,7 +131,7 @@ export const bugCollectApi = {
return client.get<{ data: BugCollectIssueRanking[] }>('/bugcollect/v1/issues/rankings/risk', { params: { appKey } })
},
// Events
// Events — eventName→name, startDate/endDate→from/to, page is 0-based from caller → +1 for backend
events(appKey: string, params: {
eventName?: string
userId?: string
@ -125,7 +140,10 @@ export const bugCollectApi = {
page?: number
size?: number
}) {
return client.get<{ data: BugCollectPageResult<BugCollectEventItem> }>('/bugcollect/v1/events', { params: { appKey, ...params } })
const { eventName, startDate, endDate, page, ...rest } = params
return client.get<{ data: BugCollectPageResult<BugCollectEventItem> }>('/bugcollect/v1/events', {
params: { appKey, ...rest, name: eventName, from: startDate, to: endDate, page: (page ?? 0) + 1 },
})
},
// Funnel

查看文件

@ -58,20 +58,22 @@
<div class="table-wrap">
<el-table :data="events" v-loading="loading" border stripe>
<el-table-column prop="eventName" label="事件名" width="180" />
<el-table-column prop="userId" label="用户 ID" width="200" show-overflow-tooltip />
<el-table-column prop="timestamp" label="时间" width="170" sortable>
<el-table-column prop="platform" label="平台" width="100" />
<el-table-column prop="appVersion" label="版本" width="110" />
<el-table-column prop="userId" label="用户 ID" width="180" show-overflow-tooltip />
<el-table-column prop="message" label="错误信息" min-width="260" show-overflow-tooltip />
<el-table-column prop="createdAt" label="时间" width="170" sortable>
<template #default="{ row }">
<span class="time-text">{{ formatTime(row.timestamp) }}</span>
<span class="time-text">{{ formatTime(row.createdAt) }}</span>
</template>
</el-table-column>
<el-table-column label="属性" min-width="300">
<el-table-column label="堆栈" width="90" align="center">
<template #default="{ row }">
<el-popover trigger="click" :width="420">
<el-popover trigger="click" :width="600">
<template #reference>
<el-button link type="primary" size="small">查看属性</el-button>
<el-button link type="primary" size="small" :disabled="!row.stack">查看</el-button>
</template>
<pre class="props-json">{{ JSON.stringify(row.properties, null, 2) }}</pre>
<pre class="props-json">{{ row.stackSymbolicated || row.stack }}</pre>
</el-popover>
</template>
</el-table-column>
@ -137,8 +139,8 @@ async function loadData() {
size: pageSize.value,
})
const data = res.data.data
events.value = data.content ?? []
total.value = data.totalElements ?? 0
events.value = data.items ?? []
total.value = data.total ?? 0
} catch {
} finally {
loading.value = false

查看文件

@ -67,7 +67,7 @@
</template>
</el-table-column>
<el-table-column prop="count" label="次数" width="100" sortable />
<el-table-column prop="affectedUsers" label="影响用户" width="110" sortable />
<el-table-column prop="isResolved" label="状态" width="90"><template #default="{ row }"><el-tag size="small" :type="row.isResolved ? 'success' : 'danger'">{{ row.isResolved ? '已解决' : '未解决' }}</el-tag></template></el-table-column>
<el-table-column prop="platform" label="平台" width="110" />
<el-table-column prop="lastSeenAt" label="最后出现" width="170" sortable>
<template #default="{ row }">
@ -146,8 +146,8 @@ async function loadData() {
size: pageSize.value,
})
const data = res.data.data
issues.value = data.content ?? []
total.value = data.totalElements ?? 0
issues.value = data.items ?? []
total.value = data.total ?? 0
} catch {
} finally {
loading.value = false

查看文件

@ -41,7 +41,7 @@
</el-table-column>
<el-table-column prop="platform" label="平台" width="110" />
<el-table-column prop="count" label="次数" width="100" sortable />
<el-table-column prop="affectedUsers" label="影响用户" width="110" sortable />
<el-table-column prop="isResolved" label="状态" width="90"><template #default="{ row }"><el-tag size="small" :type="row.isResolved ? 'success' : 'danger'">{{ row.isResolved ? '已解决' : '未解决' }}</el-tag></template></el-table-column>
<el-table-column prop="lastSeenAt" label="最后出现" width="170">
<template #default="{ row }">
<span class="time-text">{{ formatTime(row.lastSeenAt) }}</span>

查看文件

@ -46,7 +46,7 @@
</template>
</el-table-column>
<el-table-column prop="count" label="次数" width="100" sortable />
<el-table-column prop="affectedUsers" label="影响用户" width="110" sortable />
<el-table-column prop="isResolved" label="状态" width="90"><template #default="{ row }"><el-tag size="small" :type="row.isResolved ? 'success' : 'danger'">{{ row.isResolved ? '已解决' : '未解决' }}</el-tag></template></el-table-column>
<el-table-column prop="lastSeenAt" label="最后出现" width="170">
<template #default="{ row }">
<span class="time-text">{{ formatTime(row.lastSeenAt) }}</span>