init
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user