- 所有视图 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>
191 行
6.7 KiB
Vue
191 行
6.7 KiB
Vue
<template>
|
|
<div>
|
|
<div class="toolbar toolbar-space-between" style="margin-bottom: 24px">
|
|
<h2 style="margin: 0">Webhook 配置</h2>
|
|
<el-button type="primary" @click="openDialog()">新增 Webhook</el-button>
|
|
</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="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">
|
|
<el-table :data="webhooks" v-loading="loading" border stripe>
|
|
<el-table-column prop="url" label="回调地址" min-width="300" show-overflow-tooltip />
|
|
<el-table-column label="事件类型" min-width="200">
|
|
<template #default="{ row }">
|
|
<el-tag v-for="t in row.eventTypes" :key="t" size="small" style="margin-right: 4px">{{ t }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="cooldownSeconds" label="冷却(秒)" width="110" />
|
|
<el-table-column prop="enabled" label="状态" width="90">
|
|
<template #default="{ row }">
|
|
<el-tag :type="row.enabled ? 'success' : 'info'" size="small">
|
|
{{ row.enabled ? '启用' : '停用' }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作" width="160" align="center">
|
|
<template #default="{ row }">
|
|
<el-button link type="primary" size="small" @click="openDialog(row)">编辑</el-button>
|
|
<el-popconfirm title="确认删除?" @confirm="handleDelete(row.id)">
|
|
<template #reference>
|
|
<el-button link type="danger" size="small">删除</el-button>
|
|
</template>
|
|
</el-popconfirm>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</el-card>
|
|
</template>
|
|
|
|
<!-- Dialog -->
|
|
<el-dialog
|
|
v-model="dialogVisible"
|
|
:title="editingId ? '编辑 Webhook' : '新增 Webhook'"
|
|
width="520px"
|
|
destroy-on-close
|
|
>
|
|
<el-form :model="form" label-width="100px">
|
|
<el-form-item label="回调地址" required>
|
|
<el-input v-model="form.url" placeholder="https://example.com/webhook" />
|
|
</el-form-item>
|
|
<el-form-item label="事件类型" required>
|
|
<el-select v-model="form.eventTypes" multiple style="width: 100%" placeholder="选择事件类型">
|
|
<el-option label="ISSUE_NEW" value="ISSUE_NEW" />
|
|
<el-option label="ISSUE_RESOLVED" value="ISSUE_RESOLVED" />
|
|
<el-option label="CRASH_SPIKE" value="CRASH_SPIKE" />
|
|
<el-option label="ALL" value="ALL" />
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="冷却时间">
|
|
<el-input-number v-model="form.cooldownSeconds" :min="0" :max="3600" :step="60" />
|
|
<span style="margin-left: 8px; color: #999">秒</span>
|
|
</el-form-item>
|
|
<el-form-item label="启用">
|
|
<el-switch v-model="form.enabled" />
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="dialogVisible = false">取消</el-button>
|
|
<el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
<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 { ElMessage } from 'element-plus'
|
|
import { bugCollectApi, type BugCollectWebhook } from '@/api/bugcollect'
|
|
import { useBugCollectApp } from '@/composables/useBugCollectApp'
|
|
|
|
const { apps, loadingApps, appKey, setApp, gateStatus, applyDialogVisible, applyReason, applyLoading, submitActivation } = useBugCollectApp()
|
|
|
|
const webhooks = ref<BugCollectWebhook[]>([])
|
|
const loading = ref(false)
|
|
const dialogVisible = ref(false)
|
|
const saving = ref(false)
|
|
const editingId = ref('')
|
|
|
|
const form = ref({
|
|
url: '',
|
|
eventTypes: [] as string[],
|
|
cooldownSeconds: 300,
|
|
enabled: true,
|
|
})
|
|
|
|
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 ?? []
|
|
} catch {
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function openDialog(row?: BugCollectWebhook) {
|
|
if (row) {
|
|
editingId.value = row.id
|
|
form.value = {
|
|
url: row.url,
|
|
eventTypes: [...row.eventTypes],
|
|
cooldownSeconds: row.cooldownSeconds,
|
|
enabled: row.enabled,
|
|
}
|
|
} else {
|
|
editingId.value = ''
|
|
form.value = { url: '', eventTypes: [], cooldownSeconds: 300, enabled: true }
|
|
}
|
|
dialogVisible.value = true
|
|
}
|
|
|
|
async function handleSave() {
|
|
if (!form.value.url || !form.value.eventTypes.length) return
|
|
saving.value = true
|
|
try {
|
|
if (editingId.value) {
|
|
await bugCollectApi.webhooks.update(editingId.value, form.value)
|
|
ElMessage.success('更新成功')
|
|
} else {
|
|
await bugCollectApi.webhooks.create(appKey.value, form.value)
|
|
ElMessage.success('创建成功')
|
|
}
|
|
dialogVisible.value = false
|
|
loadWebhooks()
|
|
} catch {
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
|
|
async function handleDelete(id: string) {
|
|
try {
|
|
await bugCollectApi.webhooks.delete(id)
|
|
ElMessage.success('删除成功')
|
|
loadWebhooks()
|
|
} catch {}
|
|
}
|
|
|
|
watch(gateStatus, (s) => { if (s === 'enabled') loadWebhooks() })
|
|
onMounted(loadWebhooks)
|
|
</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;
|
|
}
|
|
</style>
|