This commit is contained in:
yml
2026-04-08 16:30:42 +08:00
commit 313c036845
131 changed files with 22393 additions and 0 deletions
@@ -0,0 +1,165 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { fetchAdminWebhookEventDetail, replayAdminWebhookEvent } from '@/services/admin'
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
import type { AdminWebhookEventDetail, AdminWebhookReplayResponse } from '@/types/admin'
import { hasAdminRole } from '@/utils/admin-auth'
import { stringifyDisplayJson } from '@/utils/date-time'
import { formatAdminWebhookEventType } from '@/utils/admin-webhook'
const route = useRoute()
const loading = ref(true)
const replayLoading = ref(false)
const errorMessage = ref('')
const detail = ref<AdminWebhookEventDetail | null>(null)
const lastReplayResult = ref<AdminWebhookReplayResponse['result'] | null>(null)
async function loadDetail() {
loading.value = true
errorMessage.value = ''
try {
const response = await fetchAdminWebhookEventDetail(String(route.params.eventId || ''))
detail.value = response.data
} catch (error) {
errorMessage.value = error instanceof Error ? error.message : '读取 webhook 详情失败'
} finally {
loading.value = false
}
}
async function submitReplay() {
if (!detail.value) {
return
}
try {
await ElMessageBox.confirm(
`确认重放 webhook #${detail.value.eventId} 吗?这会重新推进订单与任务处理。`,
'确认重放',
{
type: 'warning',
confirmButtonText: '继续重放',
cancelButtonText: '取消',
},
)
} catch {
return
}
replayLoading.value = true
try {
const response = await replayAdminWebhookEvent(detail.value.eventId)
lastReplayResult.value = response.data.result
ElMessage.success(`Webhook 已重放,生成/更新任务 ${response.data.result.taskCount}`)
await loadDetail()
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '重放 webhook 失败')
} finally {
replayLoading.value = false
}
}
function prettyJson(value: Record<string, unknown>) {
return stringifyDisplayJson(value, 2)
}
onMounted(loadDetail)
</script>
<template>
<section class="admin-panel">
<header class="panel-header">
<div>
<h1>Webhook 详情</h1>
<p>查看原始请求并手动重放处理流程</p>
</div>
<el-button v-if="hasAdminRole('admin')" :loading="replayLoading" round type="primary" @click="submitReplay">重放处理</el-button>
</header>
<p v-if="errorMessage" class="error-copy">{{ errorMessage }}</p>
<div v-if="loading" class="empty-block">Webhook 详情加载中</div>
<template v-else-if="detail">
<section class="info-card">
<p>ID{{ detail.eventId }} · 平台{{ detail.platform }} · 类型{{ formatAdminWebhookEventType(detail.eventType) }}</p>
<p>
验签<AdminStatusTag :status="detail.signatureValid ? 'success' : 'failed'" />
· 订单{{ detail.relatedOrderId || '-' }}
</p>
<p>
处理状态
<AdminStatusTag :status="detail.processed ? 'success' : 'failed'" />
<span v-if="!detail.processed && detail.processError" class="inline-error">{{ detail.processError }}</span>
</p>
</section>
<section v-if="lastReplayResult" class="table-card">
<h3>最近一次重放结果</h3>
<p class="result-copy">
平台订单{{ lastReplayResult.platformOrderId }} · 事件{{ formatAdminWebhookEventType(lastReplayResult.eventType) }} · 任务数{{ lastReplayResult.taskCount }}
</p>
<table class="data-table">
<thead>
<tr>
<th>任务 ID</th>
<th>任务号</th>
<th>状态</th>
</tr>
</thead>
<tbody>
<tr v-for="task in lastReplayResult.tasks" :key="task.taskId">
<td>{{ task.taskId }}</td>
<td>{{ task.taskNo }}</td>
<td><AdminStatusTag :status="task.status" /></td>
</tr>
</tbody>
</table>
</section>
<section class="table-card">
<h3>Headers</h3>
<pre class="json-block">{{ prettyJson(detail.headers) }}</pre>
</section>
<section class="table-card">
<h3>Query</h3>
<pre class="json-block">{{ prettyJson(detail.query) }}</pre>
</section>
<section class="table-card">
<h3>Body</h3>
<pre class="json-block">{{ prettyJson(detail.body) }}</pre>
</section>
</template>
</section>
</template>
<style scoped>
.admin-panel { display: grid; gap: 16px; }
.panel-header { display: flex; justify-content: space-between; gap: 16px; align-items: center; }
.panel-header h1 { margin: 0; color: #1d3555; }
.panel-header p { margin: 8px 0 0; color: #64748b; }
.info-card,.table-card,.empty-block,.error-copy {
padding: 18px; border-radius: 20px; background: rgba(255,255,255,.94); border: 1px solid rgba(86,108,138,.1);
}
.error-copy { color: #b42318; }
.inline-error { margin-left: 8px; color: #b42318; }
.result-copy { margin: 0 0 12px; color: #64748b; }
.data-table { width: 100%; border-collapse: collapse; }
.data-table th,.data-table td { padding: 12px 10px; text-align: left; border-bottom: 1px solid rgba(86,108,138,.08); color: #334155; }
.json-block {
margin: 12px 0 0;
padding: 14px;
border-radius: 14px;
background: #0f172a;
color: #e2e8f0;
overflow: auto;
font-size: 12px;
line-height: 1.6;
}
</style>