feat(bug-collect): 所有视图新增应用选择器,API 调用补充 appKey 参数
这个提交包含在:
父节点
4c6be2c489
当前提交
5e87a17765
@ -88,10 +88,11 @@ export interface BugCollectPageResult<T> {
|
|||||||
|
|
||||||
export const bugCollectApi = {
|
export const bugCollectApi = {
|
||||||
// Overview
|
// Overview
|
||||||
overview: () => client.get<{ data: BugCollectOverview }>('/bugcollect/v1/overview'),
|
overview: (appKey: string) =>
|
||||||
|
client.get<{ data: BugCollectOverview }>('/bugcollect/v1/overview', { params: { appKey } }),
|
||||||
|
|
||||||
// Issues
|
// Issues
|
||||||
issues(params: {
|
issues(appKey: string, params: {
|
||||||
type?: string
|
type?: string
|
||||||
platform?: string
|
platform?: string
|
||||||
startDate?: string
|
startDate?: string
|
||||||
@ -99,7 +100,7 @@ export const bugCollectApi = {
|
|||||||
page?: number
|
page?: number
|
||||||
size?: number
|
size?: number
|
||||||
}) {
|
}) {
|
||||||
return client.get<{ data: BugCollectPageResult<BugCollectIssue> }>('/bugcollect/v1/issues', { params })
|
return client.get<{ data: BugCollectPageResult<BugCollectIssue> }>('/bugcollect/v1/issues', { params: { appKey, ...params } })
|
||||||
},
|
},
|
||||||
|
|
||||||
issueDetail(id: string) {
|
issueDetail(id: string) {
|
||||||
@ -107,16 +108,16 @@ export const bugCollectApi = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Rankings
|
// Rankings
|
||||||
frequencyRanking() {
|
frequencyRanking(appKey: string) {
|
||||||
return client.get<{ data: BugCollectIssueRanking[] }>('/bugcollect/v1/issues/rankings/frequency')
|
return client.get<{ data: BugCollectIssueRanking[] }>('/bugcollect/v1/issues/rankings/frequency', { params: { appKey } })
|
||||||
},
|
},
|
||||||
|
|
||||||
riskRanking() {
|
riskRanking(appKey: string) {
|
||||||
return client.get<{ data: BugCollectIssueRanking[] }>('/bugcollect/v1/issues/rankings/risk')
|
return client.get<{ data: BugCollectIssueRanking[] }>('/bugcollect/v1/issues/rankings/risk', { params: { appKey } })
|
||||||
},
|
},
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
events(params: {
|
events(appKey: string, params: {
|
||||||
eventName?: string
|
eventName?: string
|
||||||
userId?: string
|
userId?: string
|
||||||
startDate?: string
|
startDate?: string
|
||||||
@ -124,21 +125,21 @@ export const bugCollectApi = {
|
|||||||
page?: number
|
page?: number
|
||||||
size?: number
|
size?: number
|
||||||
}) {
|
}) {
|
||||||
return client.get<{ data: BugCollectPageResult<BugCollectEventItem> }>('/bugcollect/v1/events', { params })
|
return client.get<{ data: BugCollectPageResult<BugCollectEventItem> }>('/bugcollect/v1/events', { params: { appKey, ...params } })
|
||||||
},
|
},
|
||||||
|
|
||||||
// Funnel
|
// Funnel
|
||||||
funnel(steps: string[]) {
|
funnel(appKey: string, steps: string[]) {
|
||||||
return client.get<{ data: BugCollectFunnelStep[] }>('/bugcollect/v1/events/funnel', {
|
return client.get<{ data: BugCollectFunnelStep[] }>('/bugcollect/v1/events/funnel', {
|
||||||
params: { steps: steps.join(',') },
|
params: { appKey, steps: steps.join(',') },
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// Webhooks
|
// Webhooks
|
||||||
webhooks: {
|
webhooks: {
|
||||||
list: () => client.get<{ data: BugCollectWebhook[] }>('/bugcollect/v1/webhooks'),
|
list: (appKey: string) => client.get<{ data: BugCollectWebhook[] }>('/bugcollect/v1/webhooks', { params: { appKey } }),
|
||||||
create: (data: Omit<BugCollectWebhook, 'id' | 'createdAt' | 'updatedAt'>) =>
|
create: (appKey: string, data: Omit<BugCollectWebhook, 'id' | 'createdAt' | 'updatedAt'>) =>
|
||||||
client.post<{ data: BugCollectWebhook }>('/bugcollect/v1/webhooks', data),
|
client.post<{ data: BugCollectWebhook }>('/bugcollect/v1/webhooks', data, { params: { appKey } }),
|
||||||
update: (id: string, data: Partial<Omit<BugCollectWebhook, 'id' | 'createdAt' | 'updatedAt'>>) =>
|
update: (id: string, data: Partial<Omit<BugCollectWebhook, 'id' | 'createdAt' | 'updatedAt'>>) =>
|
||||||
client.put<{ data: BugCollectWebhook }>(`/bugcollect/v1/webhooks/${id}`, data),
|
client.put<{ data: BugCollectWebhook }>(`/bugcollect/v1/webhooks/${id}`, data),
|
||||||
delete: (id: string) => client.delete(`/bugcollect/v1/webhooks/${id}`),
|
delete: (id: string) => client.delete(`/bugcollect/v1/webhooks/${id}`),
|
||||||
|
|||||||
@ -0,0 +1,48 @@
|
|||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { appApi, type App } from '@/api/app'
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'bugcollect_selected_app_key'
|
||||||
|
|
||||||
|
export function useBugCollectApp() {
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const apps = ref<App[]>([])
|
||||||
|
const loadingApps = ref(false)
|
||||||
|
const selectedAppKey = ref(localStorage.getItem(STORAGE_KEY) ?? '')
|
||||||
|
|
||||||
|
const appKey = computed(() => {
|
||||||
|
const q = route.query.appKey
|
||||||
|
if (typeof q === 'string' && q.trim()) return q.trim()
|
||||||
|
return selectedAppKey.value
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loadApps() {
|
||||||
|
loadingApps.value = true
|
||||||
|
try {
|
||||||
|
const res = await appApi.list()
|
||||||
|
apps.value = res.data.data ?? []
|
||||||
|
} catch {
|
||||||
|
apps.value = []
|
||||||
|
} finally {
|
||||||
|
loadingApps.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setApp(key: string) {
|
||||||
|
selectedAppKey.value = key
|
||||||
|
localStorage.setItem(STORAGE_KEY, key)
|
||||||
|
router.replace({ query: { ...route.query, appKey: key } })
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const q = route.query.appKey
|
||||||
|
if (typeof q === 'string' && q.trim()) {
|
||||||
|
selectedAppKey.value = q.trim()
|
||||||
|
localStorage.setItem(STORAGE_KEY, q.trim())
|
||||||
|
}
|
||||||
|
loadApps()
|
||||||
|
})
|
||||||
|
|
||||||
|
return { apps, loadingApps, appKey, setApp }
|
||||||
|
}
|
||||||
@ -196,10 +196,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="service-actions">
|
<div class="service-actions">
|
||||||
<template v-if="isServiceEnabled('BUG_COLLECT')">
|
<template v-if="isServiceEnabled('BUG_COLLECT')">
|
||||||
<el-button size="small" type="primary" plain @click="$router.push('/bugcollect/overview')">
|
<el-button size="small" type="primary" plain @click="$router.push({ path: '/bugcollect/overview', query: { appKey: app.appKey } })">
|
||||||
崩溃概览 →
|
崩溃概览 →
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="small" @click="$router.push('/bugcollect/issues')">
|
<el-button size="small" @click="$router.push({ path: '/bugcollect/issues', query: { appKey: app.appKey } })">
|
||||||
错误列表 →
|
错误列表 →
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -2,6 +2,22 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2 style="margin-bottom: 24px">事件流水</h2>
|
<h2 style="margin-bottom: 24px">事件流水</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="!appKey" description="请选择一个应用" style="margin-top:80px" />
|
||||||
|
<template v-else>
|
||||||
|
|
||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
<div class="toolbar responsive-toolbar">
|
<div class="toolbar responsive-toolbar">
|
||||||
<el-input
|
<el-input
|
||||||
@ -67,12 +83,16 @@
|
|||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
/>
|
/>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { bugCollectApi, type BugCollectEventItem } from '@/api/bugcollect'
|
import { bugCollectApi, type BugCollectEventItem } from '@/api/bugcollect'
|
||||||
|
import { useBugCollectApp } from '@/composables/useBugCollectApp'
|
||||||
|
|
||||||
|
const { apps, loadingApps, appKey, setApp } = useBugCollectApp()
|
||||||
|
|
||||||
const events = ref<BugCollectEventItem[]>([])
|
const events = ref<BugCollectEventItem[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@ -94,7 +114,7 @@ function formatTime(ts: string) {
|
|||||||
async function loadData() {
|
async function loadData() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await bugCollectApi.events({
|
const res = await bugCollectApi.events(appKey.value, {
|
||||||
eventName: filters.value.eventName || undefined,
|
eventName: filters.value.eventName || undefined,
|
||||||
userId: filters.value.userId || undefined,
|
userId: filters.value.userId || undefined,
|
||||||
startDate: filters.value.dateRange?.[0] || undefined,
|
startDate: filters.value.dateRange?.[0] || undefined,
|
||||||
@ -126,6 +146,8 @@ onMounted(loadData)
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.app-selector-bar { display:flex; align-items:center; gap:12px; margin-bottom:20px; }
|
||||||
|
.selector-label { font-size:14px; color:#606266; }
|
||||||
.responsive-toolbar {
|
.responsive-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|||||||
@ -2,6 +2,22 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2 style="margin-bottom: 24px">漏斗分析</h2>
|
<h2 style="margin-bottom: 24px">漏斗分析</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="!appKey" description="请选择一个应用" style="margin-top:80px" />
|
||||||
|
<template v-else>
|
||||||
|
|
||||||
<el-card style="margin-bottom: 16px">
|
<el-card style="margin-bottom: 16px">
|
||||||
<template #header>配置漏斗步骤</template>
|
<template #header>配置漏斗步骤</template>
|
||||||
<div class="funnel-builder">
|
<div class="funnel-builder">
|
||||||
@ -37,12 +53,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { bugCollectApi, type BugCollectFunnelStep } from '@/api/bugcollect'
|
import { bugCollectApi, type BugCollectFunnelStep } from '@/api/bugcollect'
|
||||||
|
import { useBugCollectApp } from '@/composables/useBugCollectApp'
|
||||||
|
|
||||||
|
const { apps, loadingApps, appKey, setApp } = useBugCollectApp()
|
||||||
|
|
||||||
const steps = ref(['', ''])
|
const steps = ref(['', ''])
|
||||||
const funnelData = ref<BugCollectFunnelStep[]>([])
|
const funnelData = ref<BugCollectFunnelStep[]>([])
|
||||||
@ -66,7 +86,7 @@ async function analyze() {
|
|||||||
if (validSteps.length < 2) return
|
if (validSteps.length < 2) return
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await bugCollectApi.funnel(validSteps)
|
const res = await bugCollectApi.funnel(appKey.value, validSteps)
|
||||||
funnelData.value = res.data.data
|
funnelData.value = res.data.data
|
||||||
} catch {
|
} catch {
|
||||||
} finally {
|
} finally {
|
||||||
@ -76,6 +96,8 @@ async function analyze() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.app-selector-bar { display:flex; align-items:center; gap:12px; margin-bottom:20px; }
|
||||||
|
.selector-label { font-size:14px; color:#606266; }
|
||||||
.funnel-builder {
|
.funnel-builder {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@ -2,6 +2,22 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2 style="margin-bottom: 24px">错误列表</h2>
|
<h2 style="margin-bottom: 24px">错误列表</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="!appKey" description="请选择一个应用" style="margin-top:80px" />
|
||||||
|
<template v-else>
|
||||||
|
|
||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
<div class="toolbar responsive-toolbar">
|
<div class="toolbar responsive-toolbar">
|
||||||
<el-select v-model="filters.type" placeholder="错误类型" style="width: 150px" clearable @change="loadData">
|
<el-select v-model="filters.type" placeholder="错误类型" style="width: 150px" clearable @change="loadData">
|
||||||
@ -66,12 +82,16 @@
|
|||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
/>
|
/>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { bugCollectApi, type BugCollectIssue } from '@/api/bugcollect'
|
import { bugCollectApi, type BugCollectIssue } from '@/api/bugcollect'
|
||||||
|
import { useBugCollectApp } from '@/composables/useBugCollectApp'
|
||||||
|
|
||||||
|
const { apps, loadingApps, appKey, setApp } = useBugCollectApp()
|
||||||
|
|
||||||
const issues = ref<BugCollectIssue[]>([])
|
const issues = ref<BugCollectIssue[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@ -103,7 +123,7 @@ function formatTime(ts: string) {
|
|||||||
async function loadData() {
|
async function loadData() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await bugCollectApi.issues({
|
const res = await bugCollectApi.issues(appKey.value, {
|
||||||
type: filters.value.type || undefined,
|
type: filters.value.type || undefined,
|
||||||
platform: filters.value.platform || undefined,
|
platform: filters.value.platform || undefined,
|
||||||
startDate: filters.value.dateRange?.[0] || undefined,
|
startDate: filters.value.dateRange?.[0] || undefined,
|
||||||
@ -135,6 +155,8 @@ onMounted(loadData)
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.app-selector-bar { display:flex; align-items:center; gap:12px; margin-bottom:20px; }
|
||||||
|
.selector-label { font-size:14px; color:#606266; }
|
||||||
.responsive-toolbar {
|
.responsive-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|||||||
@ -2,6 +2,22 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2 style="margin-bottom: 24px">Bug 概览</h2>
|
<h2 style="margin-bottom: 24px">Bug 概览</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="!appKey" description="请选择一个应用" style="margin-top:80px" />
|
||||||
|
<template v-else>
|
||||||
|
|
||||||
<!-- Stats Cards -->
|
<!-- Stats Cards -->
|
||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
@ -77,6 +93,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -84,6 +101,9 @@
|
|||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } 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'
|
||||||
|
|
||||||
|
const { apps, loadingApps, appKey, setApp } = useBugCollectApp()
|
||||||
|
|
||||||
const overview = ref<BugCollectOverview>({
|
const overview = ref<BugCollectOverview>({
|
||||||
totalIssues: 0,
|
totalIssues: 0,
|
||||||
@ -115,13 +135,15 @@ function issueTypeTag(type: string) {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await bugCollectApi.overview()
|
const res = await bugCollectApi.overview(appKey.value)
|
||||||
overview.value = res.data.data
|
overview.value = res.data.data
|
||||||
} catch {}
|
} catch {}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.app-selector-bar { display:flex; align-items:center; gap:12px; margin-bottom:20px; }
|
||||||
|
.selector-label { font-size:14px; color:#606266; }
|
||||||
.trend-chart {
|
.trend-chart {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
|
|||||||
@ -2,6 +2,22 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2 style="margin-bottom: 24px">高频错误排行</h2>
|
<h2 style="margin-bottom: 24px">高频错误排行</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="!appKey" description="请选择一个应用" style="margin-top:80px" />
|
||||||
|
<template v-else>
|
||||||
|
|
||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
<el-table :data="rankings" v-loading="loading" border stripe>
|
<el-table :data="rankings" v-loading="loading" border stripe>
|
||||||
<el-table-column type="index" label="#" width="60" />
|
<el-table-column type="index" label="#" width="60" />
|
||||||
@ -27,12 +43,16 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { bugCollectApi, type BugCollectIssueRanking } from '@/api/bugcollect'
|
import { bugCollectApi, type BugCollectIssueRanking } from '@/api/bugcollect'
|
||||||
|
import { useBugCollectApp } from '@/composables/useBugCollectApp'
|
||||||
|
|
||||||
|
const { apps, loadingApps, appKey, setApp } = useBugCollectApp()
|
||||||
|
|
||||||
const rankings = ref<BugCollectIssueRanking[]>([])
|
const rankings = ref<BugCollectIssueRanking[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@ -55,7 +75,7 @@ function formatTime(ts: string) {
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await bugCollectApi.frequencyRanking()
|
const res = await bugCollectApi.frequencyRanking(appKey.value)
|
||||||
rankings.value = res.data.data
|
rankings.value = res.data.data
|
||||||
} catch {
|
} catch {
|
||||||
} finally {
|
} finally {
|
||||||
@ -65,6 +85,8 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.app-selector-bar { display:flex; align-items:center; gap:12px; margin-bottom:20px; }
|
||||||
|
.selector-label { font-size:14px; color:#606266; }
|
||||||
.time-text {
|
.time-text {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
|||||||
@ -2,6 +2,22 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2 style="margin-bottom: 24px">高危排行</h2>
|
<h2 style="margin-bottom: 24px">高危排行</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="!appKey" description="请选择一个应用" style="margin-top:80px" />
|
||||||
|
<template v-else>
|
||||||
|
|
||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
<el-table :data="rankings" v-loading="loading" border stripe>
|
<el-table :data="rankings" v-loading="loading" border stripe>
|
||||||
<el-table-column type="index" label="#" width="60" />
|
<el-table-column type="index" label="#" width="60" />
|
||||||
@ -32,12 +48,16 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { bugCollectApi, type BugCollectIssueRanking } from '@/api/bugcollect'
|
import { bugCollectApi, type BugCollectIssueRanking } from '@/api/bugcollect'
|
||||||
|
import { useBugCollectApp } from '@/composables/useBugCollectApp'
|
||||||
|
|
||||||
|
const { apps, loadingApps, appKey, setApp } = useBugCollectApp()
|
||||||
|
|
||||||
const rankings = ref<BugCollectIssueRanking[]>([])
|
const rankings = ref<BugCollectIssueRanking[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@ -67,7 +87,7 @@ function formatTime(ts: string) {
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await bugCollectApi.riskRanking()
|
const res = await bugCollectApi.riskRanking(appKey.value)
|
||||||
rankings.value = res.data.data
|
rankings.value = res.data.data
|
||||||
} catch {
|
} catch {
|
||||||
} finally {
|
} finally {
|
||||||
@ -77,6 +97,8 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.app-selector-bar { display:flex; align-items:center; gap:12px; margin-bottom:20px; }
|
||||||
|
.selector-label { font-size:14px; color:#606266; }
|
||||||
.time-text {
|
.time-text {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
|||||||
@ -5,6 +5,22 @@
|
|||||||
<el-button type="primary" @click="openDialog()">新增 Webhook</el-button>
|
<el-button type="primary" @click="openDialog()">新增 Webhook</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 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="!appKey" description="请选择一个应用" style="margin-top:80px" />
|
||||||
|
<template v-else>
|
||||||
|
|
||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
<el-table :data="webhooks" v-loading="loading" border stripe>
|
<el-table :data="webhooks" v-loading="loading" border stripe>
|
||||||
<el-table-column prop="url" label="回调地址" min-width="300" show-overflow-tooltip />
|
<el-table-column prop="url" label="回调地址" min-width="300" show-overflow-tooltip />
|
||||||
@ -33,6 +49,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- Dialog -->
|
<!-- Dialog -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
@ -73,6 +90,9 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } 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'
|
||||||
|
|
||||||
|
const { apps, loadingApps, appKey, setApp } = useBugCollectApp()
|
||||||
|
|
||||||
const webhooks = ref<BugCollectWebhook[]>([])
|
const webhooks = ref<BugCollectWebhook[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@ -90,7 +110,7 @@ const form = ref({
|
|||||||
async function loadWebhooks() {
|
async function loadWebhooks() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await bugCollectApi.webhooks.list()
|
const res = await bugCollectApi.webhooks.list(appKey.value)
|
||||||
webhooks.value = res.data.data
|
webhooks.value = res.data.data
|
||||||
} catch {
|
} catch {
|
||||||
} finally {
|
} finally {
|
||||||
@ -122,7 +142,7 @@ async function handleSave() {
|
|||||||
await bugCollectApi.webhooks.update(editingId.value, form.value)
|
await bugCollectApi.webhooks.update(editingId.value, form.value)
|
||||||
ElMessage.success('更新成功')
|
ElMessage.success('更新成功')
|
||||||
} else {
|
} else {
|
||||||
await bugCollectApi.webhooks.create(form.value)
|
await bugCollectApi.webhooks.create(appKey.value, form.value)
|
||||||
ElMessage.success('创建成功')
|
ElMessage.success('创建成功')
|
||||||
}
|
}
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
@ -145,6 +165,8 @@ onMounted(loadWebhooks)
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<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 {
|
.toolbar-space-between {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户