feat: 添加 Sourcemap 管理页面
- 新增 BugCollectSourcemaps.vue 组件 - 添加路由 /bugcollect/sourcemaps - 添加导航菜单项 - 展示已上传的 Sourcemap 列表 - 包含使用说明(Android/RN/手动上传) Co-Authored-By: Claude <noreply@anthropic.com>
这个提交包含在:
父节点
bff566f778
当前提交
7b3ef8189e
@ -101,6 +101,16 @@ export interface BugCollectWebhookRequest {
|
|||||||
secret?: string
|
secret?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BugCollectSourcemap {
|
||||||
|
id: number
|
||||||
|
appKey: string
|
||||||
|
platform: string
|
||||||
|
appVersion: string
|
||||||
|
bundleName: string
|
||||||
|
storageKey: string
|
||||||
|
uploadedAt: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface BugCollectStatistics {
|
export interface BugCollectStatistics {
|
||||||
levelDistribution: { name: string; value: number }[]
|
levelDistribution: { name: string; value: number }[]
|
||||||
statusDistribution: { name: string; value: number }[]
|
statusDistribution: { name: string; value: number }[]
|
||||||
@ -236,4 +246,10 @@ export const bugCollectApi = {
|
|||||||
delete: (id: string | number) =>
|
delete: (id: string | number) =>
|
||||||
client.delete(`/bugcollect/v1/webhooks/${id}`),
|
client.delete(`/bugcollect/v1/webhooks/${id}`),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Sourcemaps
|
||||||
|
sourcemaps: {
|
||||||
|
list: (appKey: string) =>
|
||||||
|
client.get<{ data: BugCollectSourcemap[] }>('/bugcollect/v1/sourcemaps', { params: { appKey } }),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -142,6 +142,10 @@ const router = createRouter({
|
|||||||
path: 'bugcollect/webhooks',
|
path: 'bugcollect/webhooks',
|
||||||
component: () => import('@/views/bug-collect/BugCollectWebhooks.vue'),
|
component: () => import('@/views/bug-collect/BugCollectWebhooks.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'bugcollect/sourcemaps',
|
||||||
|
component: () => import('@/views/bug-collect/BugCollectSourcemaps.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'bugcollect/rank/freq',
|
path: 'bugcollect/rank/freq',
|
||||||
component: () => import('@/views/bug-collect/BugCollectRankFreq.vue'),
|
component: () => import('@/views/bug-collect/BugCollectRankFreq.vue'),
|
||||||
|
|||||||
@ -0,0 +1,164 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h2 style="margin-bottom: 24px">Sourcemap 管理</h2>
|
||||||
|
|
||||||
|
<!-- App selector bar -->
|
||||||
|
<div class="app-selector-bar">
|
||||||
|
<span class="selector-label">选择应用</span>
|
||||||
|
<el-select
|
||||||
|
:model-value="appKey"
|
||||||
|
placeholder="请选择应用"
|
||||||
|
style="width:220px"
|
||||||
|
:loading="loadingApps"
|
||||||
|
@change="setApp"
|
||||||
|
>
|
||||||
|
<el-option v-for="a in apps" :key="a.appKey" :label="a.name" :value="a.appKey" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<el-empty v-if="gateStatus === 'no-app'" description="请选择一个应用" style="margin-top:80px" />
|
||||||
|
<div v-else-if="gateStatus === 'loading'" v-loading="true" style="min-height:200px" />
|
||||||
|
<div v-else-if="gateStatus === 'not-enabled'" style="margin-top:60px;text-align:center">
|
||||||
|
<el-empty description="当前应用未开通崩溃收集服务">
|
||||||
|
<el-button type="primary" @click="applyDialogVisible = true">申请开通</el-button>
|
||||||
|
</el-empty>
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
|
||||||
|
<el-card shadow="never">
|
||||||
|
<template #header>
|
||||||
|
<div class="toolbar toolbar-space-between">
|
||||||
|
<span>已上传的 Sourcemap 文件</span>
|
||||||
|
<el-button @click="loadData" :loading="loading">刷新</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-table :data="sourcemaps" v-loading="loading" border stripe>
|
||||||
|
<el-table-column prop="platform" label="平台" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag size="small" :type="platformTagType(row.platform)">{{ row.platform }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="appVersion" label="应用版本" width="120" />
|
||||||
|
<el-table-column prop="bundleName" label="Bundle 名称" width="120" />
|
||||||
|
<el-table-column prop="storageKey" label="文件路径" min-width="300" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="uploadedAt" label="上传时间" width="170">
|
||||||
|
<template #default="{ row }">{{ formatTime(row.uploadedAt) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<el-empty v-if="!loading && sourcemaps.length === 0" description="暂无 Sourcemap 文件" />
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card shadow="never" style="margin-top: 16px">
|
||||||
|
<template #header>使用说明</template>
|
||||||
|
<div class="usage-guide">
|
||||||
|
<h4>Android (ProGuard Mapping)</h4>
|
||||||
|
<p>使用 Gradle 插件自动上传:</p>
|
||||||
|
<pre class="code-block">apply plugin: 'com.xuqm.bugcollect'
|
||||||
|
|
||||||
|
// 构建时自动上传 mapping.txt
|
||||||
|
./gradlew assembleRelease</pre>
|
||||||
|
|
||||||
|
<h4>React Native (JS Sourcemap)</h4>
|
||||||
|
<p>使用 Metro 插件自动上传:</p>
|
||||||
|
<pre class="code-block">// metro.config.js
|
||||||
|
const { withBugCollect } = require('@xuqm/rn-bugcollect/metro')
|
||||||
|
|
||||||
|
module.exports = withBugCollect({
|
||||||
|
// ... your metro config
|
||||||
|
})
|
||||||
|
|
||||||
|
// 构建时自动上传
|
||||||
|
npx react-native bundle --platform android --dev false</pre>
|
||||||
|
|
||||||
|
<h4>手动上传</h4>
|
||||||
|
<p>使用 curl 命令手动上传:</p>
|
||||||
|
<pre class="code-block">curl -X POST "https://your-api.com/bugcollect/v1/sourcemaps/upload" \
|
||||||
|
-F "appKey=your-app-key" \
|
||||||
|
-F "platform=android" \
|
||||||
|
-F "appVersion=1.0.0" \
|
||||||
|
-F "file=@mapping.txt"</pre>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-dialog v-model="applyDialogVisible" title="申请开通崩溃收集" width="400px" :close-on-click-modal="false">
|
||||||
|
<el-input v-model="applyReason" type="textarea" placeholder="请说明申请原因(选填)" :rows="3" />
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="applyDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="applyLoading" @click="submitActivation">提交申请</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, watch } from 'vue'
|
||||||
|
import { bugCollectApi, type BugCollectSourcemap } from '@/api/bugcollect'
|
||||||
|
import { useBugCollectApp } from '@/composables/useBugCollectApp'
|
||||||
|
|
||||||
|
const { apps, loadingApps, appKey, setApp, gateStatus, applyDialogVisible, applyReason, applyLoading, submitActivation } = useBugCollectApp()
|
||||||
|
|
||||||
|
const sourcemaps = ref<BugCollectSourcemap[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
function platformTagType(platform: string): '' | 'success' | 'warning' | 'info' | 'danger' {
|
||||||
|
const map: Record<string, '' | 'success' | 'warning' | 'info' | 'danger'> = {
|
||||||
|
android: 'success',
|
||||||
|
ios: '',
|
||||||
|
harmony: 'warning',
|
||||||
|
}
|
||||||
|
return map[platform?.toLowerCase()] ?? 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(ts: string) {
|
||||||
|
if (!ts) return '-'
|
||||||
|
const date = ts.includes('Z') || ts.includes('+') ? new Date(ts) : new Date(ts + 'Z')
|
||||||
|
return date.toLocaleString('zh-CN')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
if (!appKey.value || gateStatus.value !== 'enabled') return
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await bugCollectApi.sourcemaps.list(appKey.value)
|
||||||
|
sourcemaps.value = res.data.data ?? []
|
||||||
|
} catch {
|
||||||
|
sourcemaps.value = []
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(gateStatus, (s) => { if (s === 'enabled') loadData() })
|
||||||
|
onMounted(loadData)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-selector-bar { display:flex; align-items:center; gap:12px; margin-bottom:20px; }
|
||||||
|
.selector-label { font-size:14px; color:#606266; }
|
||||||
|
.toolbar-space-between {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.usage-guide h4 {
|
||||||
|
margin: 16px 0 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.usage-guide p {
|
||||||
|
margin: 4px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
.code-block {
|
||||||
|
background: #f5f7fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 8px 0 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -30,6 +30,7 @@
|
|||||||
<el-menu-item index="/bugcollect/rank/freq"><el-icon><Sort /></el-icon><span>高频排行</span></el-menu-item>
|
<el-menu-item index="/bugcollect/rank/freq"><el-icon><Sort /></el-icon><span>高频排行</span></el-menu-item>
|
||||||
<el-menu-item index="/bugcollect/rank/risk"><el-icon><Warning /></el-icon><span>高危排行</span></el-menu-item>
|
<el-menu-item index="/bugcollect/rank/risk"><el-icon><Warning /></el-icon><span>高危排行</span></el-menu-item>
|
||||||
<el-menu-item index="/bugcollect/webhooks"><el-icon><Link /></el-icon><span>Webhook</span></el-menu-item>
|
<el-menu-item index="/bugcollect/webhooks"><el-icon><Link /></el-icon><span>Webhook</span></el-menu-item>
|
||||||
|
<el-menu-item index="/bugcollect/sourcemaps"><el-icon><Document /></el-icon><span>Sourcemap</span></el-menu-item>
|
||||||
</el-sub-menu>
|
</el-sub-menu>
|
||||||
</el-sub-menu>
|
</el-sub-menu>
|
||||||
<el-menu-item index="/security"><el-icon><Lock /></el-icon><span>安全中心</span></el-menu-item>
|
<el-menu-item index="/security"><el-icon><Lock /></el-icon><span>安全中心</span></el-menu-item>
|
||||||
@ -80,6 +81,7 @@
|
|||||||
<el-menu-item index="/bugcollect/rank/freq"><el-icon><Sort /></el-icon><span>高频排行</span></el-menu-item>
|
<el-menu-item index="/bugcollect/rank/freq"><el-icon><Sort /></el-icon><span>高频排行</span></el-menu-item>
|
||||||
<el-menu-item index="/bugcollect/rank/risk"><el-icon><Warning /></el-icon><span>高危排行</span></el-menu-item>
|
<el-menu-item index="/bugcollect/rank/risk"><el-icon><Warning /></el-icon><span>高危排行</span></el-menu-item>
|
||||||
<el-menu-item index="/bugcollect/webhooks"><el-icon><Link /></el-icon><span>Webhook</span></el-menu-item>
|
<el-menu-item index="/bugcollect/webhooks"><el-icon><Link /></el-icon><span>Webhook</span></el-menu-item>
|
||||||
|
<el-menu-item index="/bugcollect/sourcemaps"><el-icon><Document /></el-icon><span>Sourcemap</span></el-menu-item>
|
||||||
</el-sub-menu>
|
</el-sub-menu>
|
||||||
</el-sub-menu>
|
</el-sub-menu>
|
||||||
<el-menu-item index="/security"><el-icon><Lock /></el-icon><span>安全中心</span></el-menu-item>
|
<el-menu-item index="/security"><el-icon><Lock /></el-icon><span>安全中心</span></el-menu-item>
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户