优化了订单展示

This commit is contained in:
yml
2026-04-09 02:03:39 +08:00
parent e4ab1f559e
commit 98cc0c16fc
6 changed files with 321 additions and 29 deletions
+15
View File
@@ -80,16 +80,25 @@ export interface AdminDashboardSummary {
export interface AdminOrderListItem {
orderId: number
provider: string
platform: string
shopId: string
shopName: string
platformOrderId: string
orderStatus: string
payStatus: string
buyerId: string
buyerName: string
receiverContact: string
totalAmount: string
totalAmountFen: number
currency: string
paidAt: string | null
createdAt: string
updatedAt: string
itemCount: number
totalQuantity: number
itemSummary: string
taskCount: number
systemBindingStatus: string
userBindingStatus: string
@@ -139,7 +148,10 @@ export interface AdminTaskActionResponse {
export interface AdminOrderDetail {
order: {
orderId: number
provider: string
platform: string
shopId: string
shopName: string
platformOrderId: string
orderStatus: string
payStatus: string
@@ -187,7 +199,10 @@ export interface AdminOrderDetail {
}>
webhookEvents: Array<{
eventId: number
provider: string
platform: string
shopId: string
shopName: string
eventType: string
eventKey: string
signatureValid: boolean
@@ -23,6 +23,19 @@ onMounted(async () => {
loading.value = false
}
})
function formatJsonBlock(value: Record<string, unknown>) {
return JSON.stringify(value || {}, null, 2)
}
function formatSpecSummary(spec: Record<string, unknown>) {
const entries = Object.entries(spec || {})
.filter(([, value]) => value !== null && value !== undefined && String(value).trim() !== '')
.slice(0, 4)
.map(([key, value]) => `${key}: ${String(value)}`)
return entries.length > 0 ? entries.join(' | ') : '-'
}
</script>
<template>
@@ -39,16 +52,49 @@ onMounted(async () => {
<template v-else-if="detail">
<section class="info-card">
<h2>{{ detail.order.platformOrderId }}</h2>
<p>支付状态<AdminStatusTag :status="detail.order.payStatus" /> · 订单状态<AdminStatusTag :status="detail.order.orderStatus" /></p>
<p>
系统绑定<AdminStatusTag :status="detail.order.bindingSummary.systemBindingStatus" />
· 完整绑定<AdminStatusTag :status="detail.order.bindingSummary.userBindingStatus" />
</p>
<p>
任务进度{{ detail.order.bindingSummary.completedBindingTaskCount }}/{{ detail.order.bindingSummary.totalTaskCount }} 完整绑定
· {{ detail.order.bindingSummary.systemBoundTaskCount }}/{{ detail.order.bindingSummary.totalTaskCount }} 系统绑定
</p>
<div class="info-card-header">
<div>
<h2>{{ detail.order.platformOrderId }}</h2>
<p class="subtitle">{{ detail.order.platform }} / {{ detail.order.shopName || detail.order.shopId || '未识别店铺' }}</p>
</div>
<div class="status-stack">
<AdminStatusTag :status="detail.order.payStatus" />
<AdminStatusTag :status="detail.order.orderStatus" />
<AdminStatusTag :status="detail.order.bindingSummary.systemBindingStatus" />
<AdminStatusTag :status="detail.order.bindingSummary.userBindingStatus" />
</div>
</div>
<div class="info-grid">
<article class="info-tile">
<span>订单金额</span>
<strong>{{ detail.order.totalAmount }} {{ detail.order.currency }}</strong>
<small>支付时间{{ formatAdminDateTime(detail.order.paidAt) }}</small>
</article>
<article class="info-tile">
<span>买家信息</span>
<strong>{{ detail.order.buyerName || '-' }}</strong>
<small>ID{{ detail.order.buyerId || '-' }}</small>
</article>
<article class="info-tile">
<span>联系方式</span>
<strong>{{ detail.order.receiverContact || '-' }}</strong>
<small>店铺{{ detail.order.shopName || detail.order.shopId || '-' }}</small>
</article>
<article class="info-tile">
<span>任务进度</span>
<strong>{{ detail.order.bindingSummary.completedBindingTaskCount }}/{{ detail.order.bindingSummary.totalTaskCount }} 完整绑定</strong>
<small>{{ detail.order.bindingSummary.systemBoundTaskCount }}/{{ detail.order.bindingSummary.totalTaskCount }} 系统绑定</small>
</article>
</div>
<table class="meta-table">
<tbody>
<tr><th>内部订单 ID</th><td>{{ detail.order.orderId }}</td><th>提供方</th><td>{{ detail.order.provider }}</td></tr>
<tr><th>平台</th><td>{{ detail.order.platform }}</td><th>店铺 ID</th><td>{{ detail.order.shopId || '-' }}</td></tr>
<tr><th>创建时间</th><td>{{ formatAdminDateTime(detail.order.createdAt) }}</td><th>更新时间</th><td>{{ formatAdminDateTime(detail.order.updatedAt) }}</td></tr>
</tbody>
</table>
</section>
<section class="table-card">
@@ -58,14 +104,18 @@ onMounted(async () => {
<tr>
<th>SKU</th>
<th>商品</th>
<th>发放方式</th>
<th>数量</th>
<th>规格摘要</th>
</tr>
</thead>
<tbody>
<tr v-for="item in detail.items" :key="item.orderItemId">
<td>{{ item.skuCode }}</td>
<td>{{ item.skuName }}</td>
<td>{{ item.deliveryMode }}</td>
<td>{{ item.quantity }}</td>
<td>{{ formatSpecSummary(item.spec) }}</td>
</tr>
</tbody>
</table>
@@ -80,6 +130,7 @@ onMounted(async () => {
<th>状态</th>
<th>系统绑定</th>
<th>完整绑定</th>
<th>登录方式</th>
<th>错误</th>
</tr>
</thead>
@@ -91,6 +142,7 @@ onMounted(async () => {
<td><AdminStatusTag :status="task.status" /></td>
<td><AdminStatusTag :status="task.systemBindingStatus" /></td>
<td><AdminStatusTag :status="task.userBindingStatus" /></td>
<td>{{ task.loginType || '-' }}</td>
<td>{{ task.lastError || '-' }}</td>
</tr>
</tbody>
@@ -104,6 +156,7 @@ onMounted(async () => {
<tr>
<th>事件 ID</th>
<th>类型</th>
<th>平台 / 店铺</th>
<th>验签</th>
<th>处理</th>
<th>时间</th>
@@ -115,16 +168,25 @@ onMounted(async () => {
<RouterLink :to="`/admin/webhook-events/${event.eventId}`">{{ event.eventId }}</RouterLink>
</td>
<td>{{ formatAdminWebhookEventType(event.eventType) }}</td>
<td>{{ event.platform }} / {{ event.shopName || event.shopId || '-' }}</td>
<td><AdminStatusTag :status="event.signatureValid ? 'success' : 'failed'" /></td>
<td><AdminStatusTag :status="event.processed ? 'success' : 'failed'" /></td>
<td>{{ formatAdminDateTime(event.createdAt) }}</td>
</tr>
<tr v-if="detail.webhookEvents.length === 0">
<td colspan="5">当前订单还没有关联的 webhook 记录</td>
<td colspan="6">当前订单还没有关联的 webhook 记录</td>
</tr>
</tbody>
</table>
</section>
<section class="table-card">
<h3>原始载荷</h3>
<details class="payload-box">
<summary>展开查看原始订单 JSON</summary>
<pre>{{ formatJsonBlock(detail.order.rawPayload) }}</pre>
</details>
</section>
</template>
</section>
</template>
@@ -137,6 +199,85 @@ onMounted(async () => {
padding: 18px; border-radius: 20px; background: rgba(255,255,255,.94); border: 1px solid rgba(86,108,138,.1);
}
.error-copy { color: #b42318; }
.info-card-header {
display: flex;
justify-content: space-between;
gap: 16px;
align-items: flex-start;
margin-bottom: 16px;
}
.info-card-header h2 {
margin: 0;
color: #1d3555;
}
.subtitle {
margin: 8px 0 0;
color: #64748b;
}
.status-stack {
display: flex;
flex-wrap: wrap;
gap: 6px;
justify-content: flex-end;
}
.info-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
margin-bottom: 16px;
}
.info-tile {
padding: 14px 16px;
border-radius: 16px;
background: linear-gradient(180deg, rgba(247, 250, 255, 0.98), rgba(241, 245, 249, 0.92));
border: 1px solid rgba(86,108,138,.08);
display: grid;
gap: 6px;
}
.info-tile span,
.info-tile small {
color: #64748b;
}
.info-tile strong {
color: #1d3555;
}
.meta-table,
.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); }
.meta-table th,
.meta-table td,
.data-table th,
.data-table td { padding: 12px 10px; text-align: left; border-bottom: 1px solid rgba(86,108,138,.08); }
.meta-table th,
.data-table th { color: #64748b; width: 120px; }
.payload-box summary {
cursor: pointer;
color: #1d4ed8;
font-weight: 600;
}
.payload-box pre {
margin: 12px 0 0;
padding: 14px;
overflow: auto;
border-radius: 16px;
background: #0f172a;
color: #dbeafe;
font-size: 12px;
line-height: 1.6;
}
@media (max-width: 1200px) {
.info-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 720px) {
.info-card-header {
flex-direction: column;
}
.status-stack {
justify-content: flex-start;
}
.info-grid {
grid-template-columns: 1fr;
}
}
</style>
@@ -46,6 +46,22 @@ async function loadOrders(page = pagination.value.page) {
}
onMounted(loadOrders)
function resolveBuyerLabel(item: AdminOrderListItem) {
return item.buyerName || item.buyerId || '未识别'
}
function resolveContactLabel(item: AdminOrderListItem) {
return item.receiverContact || '未提供'
}
function resolveShopLabel(item: AdminOrderListItem) {
return item.shopName || item.shopId || '未识别店铺'
}
function resolveItemSummary(item: AdminOrderListItem) {
return item.itemSummary || '未识别商品'
}
</script>
<template>
@@ -80,20 +96,35 @@ onMounted(loadOrders)
<table class="data-table">
<thead>
<tr>
<th>订单</th>
<th>平台</th>
<th>订单信息</th>
<th>平台 / 店铺</th>
<th>商品摘要</th>
<th>订单进度</th>
<th>金额</th>
<th>金额 / 时间</th>
<th>任务进度</th>
<th>创建时间</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items" :key="item.orderId">
<td>
<RouterLink :to="`/admin/orders/${item.orderId}`">{{ item.platformOrderId }}</RouterLink>
<div class="cell-stack">
<RouterLink :to="`/admin/orders/${item.orderId}`" class="primary-link">{{ item.platformOrderId }}</RouterLink>
<span class="cell-main">{{ resolveBuyerLabel(item) }}</span>
<span class="cell-sub">联系方式{{ resolveContactLabel(item) }}</span>
</div>
</td>
<td>
<div class="cell-stack">
<span class="cell-main">{{ item.platform }}</span>
<span class="cell-sub">{{ resolveShopLabel(item) }}</span>
</div>
</td>
<td>
<div class="cell-stack">
<span class="cell-main">{{ resolveItemSummary(item) }}</span>
<span class="cell-sub">{{ item.itemCount }} 合计 {{ item.totalQuantity }} </span>
</div>
</td>
<td>{{ item.platform }}</td>
<td>
<div class="status-stack">
<AdminStatusTag :status="item.payStatus" />
@@ -102,7 +133,13 @@ onMounted(loadOrders)
<AdminStatusTag :status="item.userBindingStatus" />
</div>
</td>
<td>{{ item.totalAmount }} {{ item.currency }}</td>
<td>
<div class="cell-stack">
<span class="cell-main amount-text">{{ item.totalAmount }} {{ item.currency }}</span>
<span class="cell-sub">支付{{ formatAdminDateTime(item.paidAt) }}</span>
<span class="cell-sub">创建{{ formatAdminDateTime(item.createdAt) }}</span>
</div>
</td>
<td>
<div class="progress-stack">
<span
@@ -119,7 +156,6 @@ onMounted(loadOrders)
</span>
</div>
</td>
<td>{{ formatAdminDateTime(item.createdAt) }}</td>
</tr>
</tbody>
</table>
@@ -142,6 +178,26 @@ onMounted(loadOrders)
align-items: center;
}
.cell-stack {
display: grid;
gap: 4px;
}
.primary-link,
.cell-main {
font-weight: 600;
color: #1d3555;
}
.cell-sub {
color: #64748b;
font-size: 13px;
}
.amount-text {
white-space: nowrap;
}
.progress-stack {
display: flex;
flex-wrap: wrap;