<template><div class="page-content"><div class="mb-5 flex items-start justify-between"><div><h2 class="text-xl font-semibold">通知中心</h2><p class="text-g-500 mt-1 text-sm">订阅可靠 outbox 事件并投递签名 Webhook；失败记录可追踪并支持人工重试</p></div><ElButton type="primary" @click="openCreate">新建通道</ElButton></div><ElTabs v-model="tab"><ElTabPane label="通知通道" name="channels"><ElTable v-loading="loading" :data="channels" row-key="id"><ElTableColumn prop="name" label="名称" min-width="150"/><ElTableColumn prop="webhook_url" label="Webhook" min-width="260"/><ElTableColumn label="事件模式" min-width="200"><template #default="{row}"><ElTag v-for="p in row.event_patterns" :key="p" class="mr-1">{{p}}</ElTag></template></ElTableColumn><ElTableColumn label="签名" width="90"><template #default="{row}">{{row.has_signing_secret?'HMAC':'无'}}</template></ElTableColumn><ElTableColumn label="状态" width="90"><template #default="{row}"><ElTag :type="row.enabled?'success':'info'">{{row.enabled?'启用':'停用'}}</ElTag></template></ElTableColumn><ElTableColumn label="操作" width="150"><template #default="{row}"><ElButton link type="primary" @click="openEdit(row)">编辑</ElButton><ElButton link type="danger" @click="remove(row)">删除</ElButton></template></ElTableColumn></ElTable></ElTabPane><ElTabPane label="投递记录" name="deliveries"><ElTable :data="deliveries" row-key="id"><ElTableColumn prop="channel_name" label="通道" width="130"/><ElTableColumn prop="event_type" label="事件" min-width="190"/><ElTableColumn prop="event_id" label="事件 ID" min-width="240"/><ElTableColumn label="状态" width="100"><template #default="{row}"><ElTag :type="row.status==='delivered'?'success':'danger'">{{row.status}}</ElTag></template></ElTableColumn><ElTableColumn prop="attempts" label="次数" width="70"/><ElTableColumn prop="response_status" label="HTTP" width="80"/><ElTableColumn prop="last_error" label="最近错误" min-width="200"/><ElTableColumn label="操作" width="90"><template #default="{row}"><ElButton v-if="row.status!=='delivered'" link type="primary" @click="retry(row)">重试</ElButton></template></ElTableColumn></ElTable></ElTabPane></ElTabs>
<ElDialog v-model="visible" :title="editing?'编辑通知通道':'新建通知通道'" width="680px"><ElForm label-width="120px"><ElFormItem label="名称"><ElInput v-model="form.name"/></ElFormItem><ElFormItem label="Webhook"><ElInput v-model="form.webhook_url"/></ElFormItem><ElFormItem label="签名密钥"><ElInput v-model="secret" type="password" show-password :placeholder="editing&&hasSecret?'留空以保留现有密钥':'可选；配置后使用 HMAC-SHA256'"/></ElFormItem><ElFormItem label="事件模式"><ElSelect v-model="form.event_patterns" multiple allow-create filterable class="w-full" placeholder="如 application.*、content_policy.* 或 *"/></ElFormItem><ElFormItem label="启用"><ElSwitch v-model="form.enabled"/></ElFormItem></ElForm><template #footer><ElButton @click="visible=false">取消</ElButton><ElButton type="primary" @click="save">保存</ElButton></template></ElDialog></div></template>
<script setup lang="ts">import{NotificationChannel,NotificationDelivery,NotificationInput,createNotificationChannel,deleteNotificationChannel,fetchNotificationChannels,fetchNotificationDeliveries,retryNotificationDelivery,updateNotificationChannel}from'@/api/workbench';import{ElMessage,ElMessageBox}from'element-plus';const tab=ref('channels'),loading=ref(false),visible=ref(false),editing=ref(''),secret=ref(''),hasSecret=ref(false);const channels=ref<NotificationChannel[]>([]),deliveries=ref<NotificationDelivery[]>([]);const form=reactive<NotificationInput>({name:'',webhook_url:'',event_patterns:['application.*'],enabled:true});async function load(){loading.value=true;try{[channels.value,deliveries.value]=await Promise.all([fetchNotificationChannels(),fetchNotificationDeliveries()])}finally{loading.value=false}}function openCreate(){editing.value='';hasSecret.value=false;secret.value='';Object.assign(form,{name:'',webhook_url:'',event_patterns:['application.*'],enabled:true});visible.value=true}function openEdit(row:NotificationChannel){editing.value=row.id;hasSecret.value=row.has_signing_secret;secret.value='';Object.assign(form,{name:row.name,webhook_url:row.webhook_url,event_patterns:[...row.event_patterns],enabled:row.enabled});visible.value=true}async function save(){const payload:NotificationInput={...form};if(secret.value)payload.signing_secret=secret.value;editing.value?await updateNotificationChannel(editing.value,payload):await createNotificationChannel(payload);visible.value=false;await load()}async function remove(row:NotificationChannel){await ElMessageBox.confirm(`删除通知通道“${row.name}”？`,'确认',{type:'warning'});await deleteNotificationChannel(row.id);await load()}async function retry(row:NotificationDelivery){await retryNotificationDelivery(row.id);ElMessage.success('已重新投递');await load()}onMounted(load)</script>
