可以接收到咸鱼的订单创建连接

This commit is contained in:
yml
2026-04-08 23:20:21 +08:00
parent a2b09c89e8
commit c2ec3aa6d2
10 changed files with 608 additions and 64 deletions
@@ -7,6 +7,7 @@ import { fetchAdminWebhookEventDetail, replayAdminWebhookEvent } from '@/service
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
import type { AdminWebhookEventDetail, AdminWebhookReplayResponse } from '@/types/admin'
import { hasAdminRole } from '@/utils/admin-auth'
import { formatAdminDateTime } from '@/utils/admin-time'
import { stringifyDisplayJson } from '@/utils/date-time'
import { formatAdminWebhookEventType } from '@/utils/admin-webhook'
@@ -68,6 +69,21 @@ function prettyJson(value: Record<string, unknown>) {
return stringifyDisplayJson(value, 2)
}
function formatRequestTimestamp(value: string) {
const normalized = String(value || '').trim()
if (!/^\d{10,13}$/.test(normalized)) {
return normalized || '-'
}
const epochMs = normalized.length === 13 ? Number(normalized) : Number(normalized) * 1000
return formatAdminDateTime(new Date(epochMs).toISOString())
}
function formatAmount(detail: AdminWebhookEventDetail) {
return detail.totalAmount ? `${detail.totalAmount} ${detail.currency}` : '-'
}
onMounted(loadDetail)
</script>
@@ -86,22 +102,86 @@ onMounted(loadDetail)
<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>
<div class="overview-top">
<div>
<h3>{{ formatAdminWebhookEventType(detail.eventType) }}</h3>
<p class="muted-copy">
#{{ detail.eventId }} · {{ detail.provider }} / {{ detail.platform }}
<template v-if="detail.platformRaw && detail.platformRaw !== detail.platform">
· 原始平台 {{ detail.platformRaw }}
</template>
</p>
</div>
<div class="tag-row">
<AdminStatusTag :status="detail.signatureValid ? 'success' : 'failed'" />
<AdminStatusTag :status="detail.processed ? 'success' : 'failed'" />
</div>
</div>
<div class="summary-grid">
<div class="summary-item">
<span class="summary-label">店铺</span>
<strong>{{ detail.shopName || detail.shopId || '-' }}</strong>
<span class="summary-note">ID: {{ detail.shopId || '-' }}</span>
</div>
<div class="summary-item">
<span class="summary-label">平台订单</span>
<strong>{{ detail.platformOrderId || '-' }}</strong>
<span class="summary-note">aopic: {{ detail.aopic || '-' }}</span>
</div>
<div class="summary-item">
<span class="summary-label">关联订单</span>
<strong v-if="detail.relatedOrderId">
<RouterLink class="inline-link" :to="`/admin/orders/${detail.relatedOrderId}`">{{ detail.relatedOrderId }}</RouterLink>
</strong>
<strong v-else>-</strong>
<span class="summary-note">任务数: {{ detail.taskCount }}</span>
</div>
<div class="summary-item">
<span class="summary-label">买家</span>
<strong>{{ detail.buyerName || detail.buyerId || '-' }}</strong>
<span class="summary-note">买家 ID: {{ detail.buyerId || '-' }}</span>
</div>
<div class="summary-item">
<span class="summary-label">金额</span>
<strong>{{ formatAmount(detail) }}</strong>
<span class="summary-note">商品数: {{ detail.itemCount }}</span>
</div>
<div class="summary-item">
<span class="summary-label">请求时间</span>
<strong>{{ formatRequestTimestamp(detail.requestTimestamp) }}</strong>
<span class="summary-note">入库时间: {{ formatAdminDateTime(detail.createdAt) }}</span>
</div>
<div class="summary-item">
<span class="summary-label">来源 Host</span>
<strong>{{ detail.sourceHost || '-' }}</strong>
<span class="summary-note">来源 IP: {{ detail.sourceIp || '-' }}</span>
</div>
<div class="summary-item">
<span class="summary-label">消息说明</span>
<strong>{{ detail.messageText || '-' }}</strong>
<span class="summary-note">签名: {{ detail.requestSign || '-' }}</span>
</div>
</div>
<div class="event-key-block">
<span class="summary-label">事件 Key</span>
<code>{{ detail.eventKey }}</code>
</div>
<div v-if="detail.processError" class="inline-error">{{ detail.processError }}</div>
</section>
<section v-if="lastReplayResult" class="table-card">
<h3>最近一次重放结果</h3>
<p class="result-copy">
平台订单{{ lastReplayResult.platformOrderId }} · 事件{{ formatAdminWebhookEventType(lastReplayResult.eventType) }} · 任务数{{ lastReplayResult.taskCount }}
{{ lastReplayResult.provider }} / {{ lastReplayResult.platform }}
<template v-if="lastReplayResult.platformRaw && lastReplayResult.platformRaw !== lastReplayResult.platform">
· 原始平台 {{ lastReplayResult.platformRaw }}
</template>
· 平台订单 {{ lastReplayResult.platformOrderId }}
· 事件 {{ formatAdminWebhookEventType(lastReplayResult.eventType) }}
· 任务数 {{ lastReplayResult.taskCount }}
</p>
<table class="data-table">
<thead>
@@ -121,6 +201,11 @@ onMounted(loadDetail)
</table>
</section>
<section class="table-card">
<h3>解析后的 Payload</h3>
<pre class="json-block">{{ prettyJson(detail.payload) }}</pre>
</section>
<section class="table-card">
<h3>Headers</h3>
<pre class="json-block">{{ prettyJson(detail.headers) }}</pre>
@@ -147,11 +232,69 @@ onMounted(loadDetail)
.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);
}
.overview-top { display: flex; justify-content: space-between; gap: 16px; align-items: flex-start; }
.overview-top h3 { margin: 0; color: #0f172a; }
.muted-copy { margin: 8px 0 0; color: #64748b; }
.tag-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.summary-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
margin-top: 16px;
}
.summary-item {
display: grid;
gap: 6px;
padding: 14px;
border-radius: 16px;
background: #f8fafc;
border: 1px solid rgba(148, 163, 184, 0.18);
}
.summary-label {
color: #64748b;
font-size: 12px;
font-weight: 700;
}
.summary-item strong {
color: #0f172a;
word-break: break-all;
}
.summary-note {
color: #64748b;
font-size: 12px;
line-height: 1.5;
word-break: break-all;
}
.event-key-block {
display: grid;
gap: 8px;
margin-top: 16px;
padding: 14px;
border-radius: 16px;
background: #f8fafc;
border: 1px solid rgba(148, 163, 184, 0.18);
}
.event-key-block code {
color: #334155;
font-size: 12px;
line-height: 1.6;
word-break: break-all;
white-space: pre-wrap;
}
.error-copy { color: #b42318; }
.inline-error { margin-left: 8px; color: #b42318; }
.inline-error {
margin-top: 16px;
padding: 12px 14px;
border-radius: 14px;
color: #b42318;
background: #fef3f2;
border: 1px solid #fecdca;
line-height: 1.6;
}
.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; }
.inline-link { color: #175cd3; }
.json-block {
margin: 12px 0 0;
padding: 14px;
@@ -162,4 +305,13 @@ onMounted(loadDetail)
font-size: 12px;
line-height: 1.6;
}
@media (max-width: 960px) {
.overview-top {
flex-direction: column;
}
.summary-grid {
grid-template-columns: 1fr;
}
}
</style>