ui优化-2

This commit is contained in:
yml2213
2026-05-14 20:16:04 +08:00
parent cfe2c03f1b
commit 0288e8f578
9 changed files with 665 additions and 617 deletions
@@ -39,17 +39,10 @@ const activeTab = computed<FulfillmentTabKey>({
<template>
<section class="hub-panel">
<header class="hub-hero">
<div>
<h1>履约配置中心</h1>
<p>旧规则和快手 Cloud 新流程都放在这里维护不再拆成两个侧边栏入口</p>
</div>
<div class="hero-tags">
<span>一个入口</span>
<span>两套配置并存</span>
<span>互不覆盖</span>
</div>
</header>
<AdminPageHeader
title="履约配置中心"
description="旧规则和快手 Cloud 新流程都放在这里维护,不再拆成两个侧边栏入口。"
/>
<section class="hub-card">
<div class="tab-copy">
@@ -72,64 +65,28 @@ const activeTab = computed<FulfillmentTabKey>({
<style scoped>
.hub-panel {
display: grid;
gap: 16px;
gap: var(--space-4);
}
.hub-hero,
.hub-card {
padding: 20px;
border-radius: 24px;
border: 1px solid rgba(86, 108, 138, 0.12);
background: rgba(255, 255, 255, 0.94);
padding: var(--space-5);
border-radius: var(--radius-xl);
border: 1px solid var(--border-default);
background: var(--bg-surface);
}
.hub-hero {
display: grid;
gap: 14px;
background:
radial-gradient(circle at top right, rgba(26, 127, 255, 0.1), transparent 26%),
linear-gradient(135deg, rgba(255,255,255,.98), rgba(240,247,255,.96));
}
.hub-hero h1,
.tab-copy h3 {
margin: 0;
color: #17324d;
color: var(--text-primary);
}
.hub-hero p,
.tab-copy p {
margin: 8px 0 0;
color: #64748b;
margin: var(--space-2) 0 0;
color: var(--text-secondary);
line-height: 1.6;
}
.hero-tags {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.hero-tags span {
padding: 8px 12px;
border-radius: 999px;
background: rgba(23, 92, 211, 0.08);
color: #175cd3;
font-size: 12px;
font-weight: 700;
}
.hub-card {
display: grid;
gap: 16px;
}
.tab-copy {
display: grid;
gap: 4px;
}
:deep(.hub-tabs .el-tabs__content) {
padding-top: 12px;
padding-top: var(--space-3);
}
</style>
@@ -47,10 +47,8 @@ async function submitLogin() {
<label class="field">
<span>账号</span>
<input
<el-input
v-model="username"
type="text"
class="text-input"
placeholder="输入账号"
@keyup.enter="submitLogin"
/>
@@ -58,11 +56,11 @@ async function submitLogin() {
<label class="field">
<span>密码</span>
<input
<el-input
v-model="password"
type="password"
class="text-input"
placeholder="输入密码"
show-password
@keyup.enter="submitLogin"
/>
</label>
@@ -79,58 +77,50 @@ async function submitLogin() {
min-height: 100vh;
display: grid;
place-items: center;
padding: 24px;
padding: var(--space-4);
background:
radial-gradient(circle at top left, rgba(18, 102, 214, 0.18), transparent 28%),
radial-gradient(circle at bottom right, rgba(12, 74, 110, 0.16), transparent 26%),
linear-gradient(160deg, #eef5ff 0%, #f7fbff 42%, #edf6f3 100%);
radial-gradient(circle at top left, rgba(59, 130, 246, 0.12), transparent 28%),
radial-gradient(circle at bottom right, rgba(59, 130, 246, 0.08), transparent 26%),
linear-gradient(160deg, var(--color-primary-light) 0%, #f7fbff 42%, #edf6f3 100%);
}
.login-card {
width: min(460px, 100%);
padding: 28px;
border-radius: 28px;
border: 1px solid rgba(38, 88, 155, 0.14);
background: rgba(255, 255, 255, 0.92);
box-shadow: 0 24px 80px rgba(15, 23, 42, 0.08);
border-radius: var(--radius-xl);
border: 1px solid var(--border-default);
background: var(--bg-surface);
box-shadow: var(--shadow-lg);
}
.login-header h1 {
margin: 8px 0 0;
color: #17324f;
margin: var(--space-2) 0 0;
color: var(--text-primary);
}
.login-header p {
margin: 10px 0 0;
color: #5f7289;
color: var(--text-secondary);
}
.eyebrow {
margin: 0;
color: #1266d6;
color: var(--color-primary);
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 12px;
font-size: var(--text-xs);
font-weight: 700;
}
.field {
margin-top: 24px;
margin-top: var(--space-6);
display: grid;
gap: 10px;
color: #334155;
}
.text-input {
min-height: 48px;
padding: 0 16px;
border-radius: 16px;
border: 1px solid rgba(86, 108, 138, 0.2);
background: rgba(255, 255, 255, 0.96);
color: var(--text-primary);
}
.submit-btn {
width: 100%;
margin-top: 20px;
margin-top: var(--space-5);
}
</style>
@@ -77,34 +77,31 @@ const taskDetailLink = computed(() => (task.value?.taskId ? `/admin/tasks/${task
<template>
<section class="admin-panel manual-redeem-panel">
<header class="panel-header">
<div>
<h1>人工兑换</h1>
<p>客服在后台创建唯一任务发送二维码复核角色并完成兑换</p>
</div>
<div class="header-actions">
<RouterLink v-if="taskDetailLink" :to="taskDetailLink" class="task-link">
查看任务详情
</RouterLink>
<el-button
v-if="hasActiveTask"
:loading="closeLoading"
plain
type="danger"
@click="closeTaskNow"
>
关闭当前任务
</el-button>
<el-button
v-if="canStartNextTask"
plain
type="primary"
@click="resetCurrentTask"
>
开始下一单
</el-button>
</div>
</header>
<AdminPageHeader
title="人工兑换"
description="客服在后台创建唯一任务、发送二维码、复核角色并完成兑换。"
>
<template v-if="taskDetailLink">
<RouterLink :to="taskDetailLink" class="task-link">查看任务详情</RouterLink>
</template>
<el-button
v-if="hasActiveTask"
:loading="closeLoading"
plain
type="danger"
@click="closeTaskNow"
>
关闭当前任务
</el-button>
<el-button
v-if="canStartNextTask"
plain
type="primary"
@click="resetCurrentTask"
>
开始下一单
</el-button>
</AdminPageHeader>
<section class="create-card">
<div class="card-head">
@@ -117,19 +114,17 @@ const taskDetailLink = computed(() => (task.value?.taskId ? `/admin/tasks/${task
<div class="form-grid">
<div class="field">
<span>唯一凭据</span>
<input
<el-input
v-model="form.proofValue"
class="text-input"
placeholder="输入订单号、客户名或其他唯一可追踪信息"
/>
</div>
<div class="field">
<span>内部履约 SKU</span>
<input
<el-input
v-model="form.skuCode"
list="manual-redeem-sku-suggestions"
class="text-input"
placeholder="选择或输入内部履约 SKU"
/>
<datalist id="manual-redeem-sku-suggestions">
@@ -144,9 +139,10 @@ const taskDetailLink = computed(() => (task.value?.taskId ? `/admin/tasks/${task
<div class="field field-remark">
<span>备注</span>
<textarea
<el-input
v-model="form.remark"
class="text-input text-area"
type="textarea"
:rows="3"
placeholder="可选:记录客户补充信息、兑换要求或客服备注"
/>
</div>
@@ -333,13 +329,12 @@ const taskDetailLink = computed(() => (task.value?.taskId ? `/admin/tasks/${task
gap: 18px;
}
.panel-header,
.header-actions,
.card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
gap: var(--space-4);
}
.header-actions {
@@ -347,7 +342,7 @@ const taskDetailLink = computed(() => (task.value?.taskId ? `/admin/tasks/${task
}
.task-link {
color: #245db6;
color: var(--color-primary);
text-decoration: none;
font-weight: 600;
}
@@ -355,22 +350,22 @@ const taskDetailLink = computed(() => (task.value?.taskId ? `/admin/tasks/${task
.create-card,
.summary-card,
.review-card {
padding: 22px 24px;
border-radius: 24px;
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(86, 108, 138, 0.1);
box-shadow: 0 20px 60px rgba(30, 49, 78, 0.08);
padding: 22px var(--space-6);
border-radius: var(--radius-xl);
background: var(--bg-surface);
border: 1px solid var(--border-default);
box-shadow: var(--shadow-lg);
}
.card-head h2 {
margin: 0;
font-size: 18px;
color: #203551;
font-size: var(--text-lg);
color: var(--text-primary);
}
.card-head p {
margin: 6px 0 0;
color: #64748b;
color: var(--text-secondary);
line-height: 1.7;
}
@@ -378,17 +373,17 @@ const taskDetailLink = computed(() => (task.value?.taskId ? `/admin/tasks/${task
margin-top: 18px;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
gap: var(--space-4);
}
.field {
display: grid;
gap: 8px;
gap: var(--space-2);
}
.field span {
color: #4a607f;
font-size: 13px;
color: var(--text-secondary);
font-size: var(--text-sm);
font-weight: 600;
}
@@ -396,58 +391,41 @@ const taskDetailLink = computed(() => (task.value?.taskId ? `/admin/tasks/${task
grid-column: 1 / -1;
}
.text-input {
width: 100%;
min-height: 46px;
padding: 0 14px;
border-radius: 14px;
border: 1px solid #dbe4ee;
background: #f8fbff;
color: #21344d;
font-size: 14px;
}
.text-area {
min-height: 96px;
padding: 12px 14px;
resize: vertical;
}
.action-row {
margin-top: 18px;
display: flex;
gap: 12px;
gap: var(--space-3);
}
.summary-card {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
gap: var(--space-3);
}
.summary-card article {
padding: 14px;
border-radius: 16px;
background: #f7f9fc;
border-radius: var(--radius-md);
background: var(--bg-page);
}
.summary-card span {
display: block;
font-size: 12px;
color: #708096;
font-size: var(--text-xs);
color: var(--text-muted);
}
.summary-card strong {
display: block;
margin-top: 8px;
color: #203551;
margin-top: var(--space-2);
color: var(--text-primary);
word-break: break-word;
}
.layout-grid {
display: grid;
grid-template-columns: minmax(340px, 392px) minmax(0, 1fr);
gap: 16px;
gap: var(--space-4);
align-items: stretch;
}
@@ -457,10 +435,10 @@ const taskDetailLink = computed(() => (task.value?.taskId ? `/admin/tasks/${task
.review-stage {
margin-top: 18px;
border-radius: 20px;
border-radius: var(--radius-lg);
overflow: hidden;
border: 1px solid #dbe4ee;
background: #f8fbff;
border: 1px solid var(--border-default);
background: var(--bg-page);
}
.screenshot-button {
@@ -481,27 +459,27 @@ const taskDetailLink = computed(() => (task.value?.taskId ? `/admin/tasks/${task
.empty-block {
min-height: 180px;
padding: 24px;
border-radius: 24px;
padding: var(--space-6);
border-radius: var(--radius-xl);
display: grid;
place-items: center;
text-align: center;
background: rgba(255, 255, 255, 0.92);
border: 1px dashed #d5dfec;
color: #64748b;
background: var(--bg-surface);
border: 1px dashed var(--border-default);
color: var(--text-secondary);
}
.empty-block strong {
color: #24384f;
font-size: 16px;
color: var(--text-primary);
font-size: var(--text-lg);
}
.empty-block p {
margin: 8px 0 0;
margin: var(--space-2) 0 0;
line-height: 1.7;
}
@media (max-width: 1140px) {
@media (max-width: 900px) {
.summary-card,
.layout-grid,
.form-grid {
@@ -92,15 +92,14 @@ function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) {
<template>
<section class="admin-panel">
<header class="panel-header">
<div>
<h1>订单详情</h1>
<p>查看订单原始信息商品子项和关联任务</p>
</div>
</header>
<AdminPageHeader
title="订单详情"
description="查看订单原始信息、商品子项和关联任务。"
/>
<p v-if="errorMessage" class="error-copy">{{ errorMessage }}</p>
<div v-if="loading" class="empty-block">订单详情加载中</div>
<el-skeleton v-if="loading" :rows="8" animated />
<template v-else-if="detail">
<section class="info-card">
@@ -163,113 +162,124 @@ function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) {
</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>
<el-table :data="[detail.order]" size="small" :show-header="false">
<el-table-column label="内部订单 ID" width="120">
<template #default="{ row }">{{ row.orderId }}</template>
</el-table-column>
<el-table-column label="提供方" width="120">
<template #default="{ row }">{{ row.provider }}</template>
</el-table-column>
<el-table-column label="平台" width="100">
<template #default="{ row }">{{ row.platform }}</template>
</el-table-column>
<el-table-column label="店铺 ID">
<template #default="{ row }">{{ row.shopId || '-' }}</template>
</el-table-column>
<el-table-column label="创建时间" width="180">
<template #default="{ row }">{{ formatAdminDateTime(row.createdAt) }}</template>
</el-table-column>
<el-table-column label="更新时间" width="180">
<template #default="{ row }">{{ formatAdminDateTime(row.updatedAt) }}</template>
</el-table-column>
</el-table>
</section>
<section class="table-card">
<section class="info-card">
<h3>商品子项</h3>
<table class="data-table">
<thead>
<tr>
<th>SKU</th>
<th>标题</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.itemTitle || '-' }}</td>
<td>{{ item.skuName }}</td>
<td>{{ item.deliveryMode }}</td>
<td>{{ item.quantity }}</td>
<td>{{ formatSpecSummary(item.spec) }}</td>
</tr>
</tbody>
</table>
<el-table :data="detail.items" size="small">
<el-table-column prop="skuCode" label="SKU" />
<el-table-column prop="itemTitle" label="标题">
<template #default="{ row }">{{ row.itemTitle || '-' }}</template>
</el-table-column>
<el-table-column prop="skuName" label="商品" />
<el-table-column prop="deliveryMode" label="发放方式" />
<el-table-column prop="quantity" label="数量" width="80" />
<el-table-column label="规格摘要">
<template #default="{ row }">{{ formatSpecSummary(row.spec) }}</template>
</el-table-column>
</el-table>
</section>
<section class="table-card">
<section class="info-card">
<h3>关联任务</h3>
<table class="data-table">
<thead>
<tr>
<th>任务号</th>
<th>状态</th>
<th>系统绑定</th>
<th>完整绑定</th>
<th>自动发货</th>
<th>库存绑定</th>
<th>登录方式</th>
<th>错误</th>
</tr>
</thead>
<tbody>
<tr v-for="task in detail.tasks" :key="task.taskId">
<td>
<RouterLink :to="`/admin/tasks/${task.taskId}`">{{ task.taskNo }}</RouterLink>
</td>
<td><AdminStatusTag :status="task.status" /></td>
<td><AdminStatusTag :status="task.systemBindingStatus" /></td>
<td><AdminStatusTag :status="task.userBindingStatus" /></td>
<td>
<div class="tile-tag-line">
<AdminStatusTag :status="resolveTaskAutoDeliveryStatus(task)" />
</div>
<div class="binding-copy">{{ formatTaskAutoDeliveryReason(task) }}</div>
</td>
<td>
<div>{{ formatTaskBindingCountSummary(task) }}</div>
<div class="binding-copy">角色 {{ formatBindingRoleSummary(task.bindingSummary.roleKeys) }}</div>
</td>
<td>{{ task.loginType || '-' }}</td>
<td>{{ task.lastError || '-' }}</td>
</tr>
</tbody>
</table>
<el-table :data="detail.tasks" size="small">
<el-table-column label="任务号">
<template #default="{ row }">
<RouterLink :to="`/admin/tasks/${row.taskId}`">{{ row.taskNo }}</RouterLink>
</template>
</el-table-column>
<el-table-column label="状态">
<template #default="{ row }">
<AdminStatusTag :status="row.status" />
</template>
</el-table-column>
<el-table-column label="系统绑定">
<template #default="{ row }">
<AdminStatusTag :status="row.systemBindingStatus" />
</template>
</el-table-column>
<el-table-column label="完整绑定">
<template #default="{ row }">
<AdminStatusTag :status="row.userBindingStatus" />
</template>
</el-table-column>
<el-table-column label="自动发货">
<template #default="{ row }">
<div class="tile-tag-line">
<AdminStatusTag :status="resolveTaskAutoDeliveryStatus(row)" />
</div>
<div class="binding-copy">{{ formatTaskAutoDeliveryReason(row) }}</div>
</template>
</el-table-column>
<el-table-column label="库存绑定">
<template #default="{ row }">
<div>{{ formatTaskBindingCountSummary(row) }}</div>
<div class="binding-copy">角色 {{ formatBindingRoleSummary(row.bindingSummary.roleKeys) }}</div>
</template>
</el-table-column>
<el-table-column label="登录方式">
<template #default="{ row }">{{ row.loginType || '-' }}</template>
</el-table-column>
<el-table-column label="错误">
<template #default="{ row }">{{ row.lastError || '-' }}</template>
</el-table-column>
</el-table>
</section>
<section class="table-card">
<section class="info-card">
<h3>Webhook 摘要</h3>
<table class="data-table">
<thead>
<tr>
<th>事件 ID</th>
<th>类型</th>
<th>平台 / 店铺</th>
<th>验签</th>
<th>处理</th>
<th>时间</th>
</tr>
</thead>
<tbody>
<tr v-for="event in detail.webhookEvents" :key="event.eventId">
<td>
<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="6">当前订单还没有关联的 webhook 记录</td>
</tr>
</tbody>
</table>
<el-table :data="detail.webhookEvents" size="small">
<el-table-column label="事件 ID">
<template #default="{ row }">
<RouterLink :to="`/admin/webhook-events/${row.eventId}`">{{ row.eventId }}</RouterLink>
</template>
</el-table-column>
<el-table-column label="类型">
<template #default="{ row }">{{ formatAdminWebhookEventType(row.eventType) }}</template>
</el-table-column>
<el-table-column label="平台 / 店铺">
<template #default="{ row }">{{ row.platform }} / {{ row.shopName || row.shopId || '-' }}</template>
</el-table-column>
<el-table-column label="验签">
<template #default="{ row }">
<AdminStatusTag :status="row.signatureValid ? 'success' : 'failed'" />
</template>
</el-table-column>
<el-table-column label="处理">
<template #default="{ row }">
<AdminStatusTag :status="row.processed ? 'success' : 'failed'" />
</template>
</el-table-column>
<el-table-column label="时间" width="180">
<template #default="{ row }">{{ formatAdminDateTime(row.createdAt) }}</template>
</el-table-column>
<template #empty>
<el-empty description="当前订单还没有关联的 webhook 记录" />
</template>
</el-table>
</section>
<section class="table-card">
<section class="info-card">
<h3>原始载荷</h3>
<details class="payload-box">
<summary>展开查看原始订单 JSON</summary>
@@ -281,99 +291,124 @@ function formatTaskAutoDeliveryReason(task: AdminOrderDetail['tasks'][number]) {
</template>
<style scoped>
.admin-panel { display: grid; gap: 16px; }
.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);
.admin-panel {
display: grid;
gap: var(--space-4);
}
.error-copy { color: #b42318; }
.info-card {
padding: 18px;
border-radius: var(--radius-lg);
background: var(--bg-surface);
border: 1px solid var(--border-default);
}
.info-card h3 {
margin: 0 0 var(--space-4);
color: var(--text-primary);
}
.error-copy {
padding: 18px;
border-radius: var(--radius-lg);
background: var(--bg-surface);
border: 1px solid var(--border-default);
color: var(--color-danger);
}
.info-card-header {
display: flex;
justify-content: space-between;
gap: 16px;
gap: var(--space-4);
align-items: flex-start;
margin-bottom: 16px;
margin-bottom: var(--space-4);
}
.info-card-header h2 {
margin: 0;
color: #1d3555;
color: var(--text-primary);
}
.subtitle {
margin: 8px 0 0;
color: #64748b;
margin: var(--space-2) 0 0;
color: var(--text-secondary);
}
.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;
gap: var(--space-3);
margin-bottom: var(--space-4);
}
.info-tile {
padding: 14px 16px;
border-radius: 16px;
padding: 14px var(--space-4);
border-radius: var(--radius-md);
background: linear-gradient(180deg, rgba(247, 250, 255, 0.98), rgba(241, 245, 249, 0.92));
border: 1px solid rgba(86,108,138,.08);
border: 1px solid var(--border-default);
display: grid;
gap: 6px;
}
.info-tile span,
.info-tile small {
color: #64748b;
color: var(--text-secondary);
}
.info-tile strong {
color: #1d3555;
color: var(--text-primary);
}
.tile-tag-line {
display: flex;
align-items: center;
}
.binding-copy {
margin-top: 6px;
color: #64748b;
font-size: 12px;
color: var(--text-secondary);
font-size: var(--text-xs);
}
.meta-table,
.data-table { width: 100%; border-collapse: collapse; }
.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;
color: var(--color-primary);
font-weight: 600;
}
.payload-box pre {
margin: 12px 0 0;
margin: var(--space-3) 0 0;
padding: 14px;
overflow: auto;
border-radius: 16px;
border-radius: var(--radius-md);
background: #0f172a;
color: #dbeafe;
font-size: 12px;
font-size: var(--text-xs);
line-height: 1.6;
}
@media (max-width: 1200px) {
.info-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 720px) {
@media (max-width: 900px) {
.info-card-header {
flex-direction: column;
}
.status-stack {
justify-content: flex-start;
}
.info-grid {
grid-template-columns: 1fr;
}
@@ -283,7 +283,7 @@ async function submitKuaishouCloudDispatch() {
await runAction(
() => dispatchAdminTaskKuaishouCloudFulfillment(detail.value!.task.taskId),
'已完成绑定确认并发货',
`确认客户已经完成绑定,并立即为任务 ${detail.value.task.taskNo} 执行发货吗?这个动作会把确认绑定完成”和“发货合并为一步。`,
`确认客户已经完成绑定,并立即为任务 ${detail.value.task.taskNo} 执行发货吗?这个动作会把"确认绑定完成"和"发货"合并为一步。`,
)
}
@@ -479,15 +479,14 @@ onBeforeUnmount(clearScreenshotPreview)
<template>
<section class="admin-panel">
<header class="panel-header">
<div>
<h1>任务详情</h1>
<p>查看任务全量信息并执行人工操作</p>
</div>
</header>
<AdminPageHeader
title="任务详情"
description="查看任务全量信息并执行人工操作。"
/>
<p v-if="errorMessage" class="error-copy">{{ errorMessage }}</p>
<div v-if="loading" class="empty-block">任务详情加载中</div>
<el-skeleton v-if="loading" :rows="6" animated />
<template v-else-if="detail">
<section v-if="isKuaishouCloudTask && kuaishouCloudFlow" class="hero-card">
@@ -652,7 +651,7 @@ onBeforeUnmount(clearScreenshotPreview)
</el-button>
</section>
<section v-if="!isKuaishouCloudTask" class="table-card">
<section v-if="!isKuaishouCloudTask" class="info-card">
<h3>任务信息</h3>
<table class="data-table">
<tbody>
@@ -686,7 +685,7 @@ onBeforeUnmount(clearScreenshotPreview)
</table>
</section>
<section v-if="isKuaishouCloudTask && kuaishouCloudFlow" class="table-card flow-card">
<section v-if="isKuaishouCloudTask && kuaishouCloudFlow" class="info-card flow-card">
<div class="flow-card-head">
<div>
<h3>快手 Cloud 履约面板</h3>
@@ -827,7 +826,7 @@ onBeforeUnmount(clearScreenshotPreview)
<div><span>商品标题</span><strong>{{ kuaishouCloudFlow.ticket.goodsTitle || '-' }}</strong></div>
<div><span>剩余次数</span><strong>{{ kuaishouCloudFlow.ticket.leftCount || 0 }}</strong></div>
</div>
<p class="manual-hint">确认绑定完成并发货已经合并为一个按钮只有核销码通过校验后才能继续</p>
<p class="manual-hint">"确认绑定完成并发货"已经合并为一个按钮只有核销码通过校验后才能继续</p>
</el-collapse-item>
<el-collapse-item name="cloud" title="Cloud 资源与发货">
@@ -871,7 +870,7 @@ onBeforeUnmount(clearScreenshotPreview)
<section
v-if="detail.order?.provider === 'agiso' && detail.order?.platform === 'xianyu'"
class="table-card"
class="info-card"
>
<h3>咸鱼自动发货</h3>
<p class="section-copy">这里展示这条任务最近一次触发咸鱼自动发货的状态原因和接口回执摘要</p>
@@ -907,7 +906,7 @@ onBeforeUnmount(clearScreenshotPreview)
</div>
</section>
<section v-if="detail.redeemResolution" class="table-card">
<section v-if="detail.redeemResolution" class="info-card">
<h3>兑换重试链路</h3>
<p class="section-copy">这里记录自动兑换时每次使用过的 CDK判定结果以及是否切换到了新的同类型凭据</p>
@@ -963,7 +962,7 @@ onBeforeUnmount(clearScreenshotPreview)
</div>
</section>
<section v-if="!isKuaishouCloudTask" class="table-card">
<section v-if="!isKuaishouCloudTask" class="info-card">
<h3>库存绑定</h3>
<p class="section-copy">这里展示任务当前和历史绑定过的库存项便于核对多库存任务的真实履约轨迹</p>
<table class="data-table">
@@ -1031,7 +1030,7 @@ onBeforeUnmount(clearScreenshotPreview)
</table>
</section>
<section v-if="detail.task.executorKey === 'manual_dispatch'" class="table-card">
<section v-if="detail.task.executorKey === 'manual_dispatch'" class="info-card">
<h3>人工履约回写</h3>
<div class="manual-grid">
<label class="field-block">
@@ -1076,7 +1075,7 @@ onBeforeUnmount(clearScreenshotPreview)
</div>
</section>
<section v-if="detail.events.length > 0" class="table-card">
<section v-if="detail.events.length > 0" class="info-card">
<h3>任务事件</h3>
<table class="data-table">
<tbody>
@@ -1091,7 +1090,7 @@ onBeforeUnmount(clearScreenshotPreview)
</table>
</section>
<section v-if="detail.screenshotUrl" class="table-card">
<section v-if="detail.screenshotUrl" class="info-card">
<h3>{{ screenshotSectionTitle }}</h3>
<p v-if="!screenshotPreviewUrl" class="screenshot-empty">截图加载中或当前不可读取</p>
<a v-else class="screenshot-link" :href="screenshotPreviewUrl" target="_blank" rel="noreferrer">打开原图</a>
@@ -1102,166 +1101,288 @@ onBeforeUnmount(clearScreenshotPreview)
</template>
<style scoped>
.admin-panel { display: grid; gap: 16px; }
.panel-header h1 { margin: 0; color: #1d3555; }
.panel-header p { margin: 8px 0 0; color: #64748b; }
.claim-link { color: #175cd3; text-decoration: none; word-break: break-all; }
.info-card,.hero-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);
.admin-panel {
display: grid;
gap: var(--space-4);
}
.claim-link {
color: var(--color-primary);
text-decoration: none;
word-break: break-all;
}
.info-card,
.hero-card,
.error-copy {
padding: 18px;
border-radius: var(--radius-lg);
background: var(--bg-surface);
border: 1px solid var(--border-default);
}
.info-card p {
margin: 10px 0 0;
color: #334155;
color: var(--text-primary);
}
.info-card h3 {
margin: 0 0 var(--space-3);
color: var(--text-primary);
}
.hero-card {
display: grid;
gap: 16px;
gap: var(--space-4);
background:
radial-gradient(circle at top right, rgba(14, 165, 233, 0.16), transparent 26%),
radial-gradient(circle at top right, rgba(14, 165, 233, 0.12), transparent 26%),
linear-gradient(135deg, rgba(255,255,255,.98), rgba(239,246,255,.96));
}
.hero-copy {
display: grid;
gap: 6px;
}
.hero-copy h2 {
margin: 0;
color: #0f172a;
color: var(--text-primary);
font-size: 30px;
line-height: 1.1;
}
.hero-copy p,
.hero-inline span,
.hero-link-row span {
color: #475467;
color: var(--text-secondary);
}
.hero-eyebrow {
color: #175cd3;
font-size: 12px;
color: var(--color-primary);
font-size: var(--text-xs);
font-weight: 700;
letter-spacing: .08em;
text-transform: uppercase;
}
.hero-meta-grid,
.overview-grid {
.hero-meta-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 12px;
gap: var(--space-3);
}
.hero-metric {
padding: 14px 16px;
padding: 14px var(--space-4);
border-radius: 18px;
border: 1px solid rgba(86,108,138,.1);
border: 1px solid var(--border-default);
background: rgba(255,255,255,.82);
}
.hero-metric span {
display: block;
margin-bottom: 8px;
color: #64748b;
font-size: 12px;
margin-bottom: var(--space-2);
color: var(--text-secondary);
font-size: var(--text-xs);
}
.hero-metric strong {
color: #0f172a;
font-size: 18px;
color: var(--text-primary);
font-size: var(--text-lg);
line-height: 1.3;
word-break: break-word;
}
.hero-metric--blue { box-shadow: inset 0 0 0 1px rgba(59,130,246,.08); }
.hero-metric--green { box-shadow: inset 0 0 0 1px rgba(16,185,129,.08); }
.hero-metric--amber { box-shadow: inset 0 0 0 1px rgba(245,158,11,.08); }
.hero-metric--slate { box-shadow: inset 0 0 0 1px rgba(100,116,139,.08); }
.hero-inline,
.hero-link-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
gap: var(--space-3);
align-items: center;
}
.error-copy { color: #b42318; }
.action-row { display: flex; flex-wrap: wrap; gap: 12px; }
.table-card h3 { margin: 0 0 12px; color: #1d3555; }
.compact-card { padding: 18px; }
.section-copy { margin: 0 0 12px; color: #64748b; }
.auto-delivery-tag-line { display: flex; align-items: center; }
.error-copy {
color: var(--color-danger);
}
.action-row {
display: flex;
flex-wrap: wrap;
gap: var(--space-3);
}
.section-copy {
margin: 0 0 var(--space-3);
color: var(--text-secondary);
}
.auto-delivery-tag-line {
display: flex;
align-items: center;
}
.auto-delivery-note {
margin-top: 14px;
padding: 14px 16px;
border-radius: 16px;
padding: 14px var(--space-4);
border-radius: var(--radius-md);
background: rgba(248,250,252,.92);
border: 1px solid rgba(86,108,138,.08);
border: 1px solid var(--border-default);
}
.auto-delivery-note p { margin: 0 0 8px; color: #334155; }
.auto-delivery-note p:last-child { margin-bottom: 0; }
.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); }
.data-table th { width: 160px; color: #64748b; }
.status-stack { display: flex; flex-wrap: wrap; gap: 8px; }
.auto-delivery-note p {
margin: 0 0 var(--space-2);
color: var(--text-primary);
}
.auto-delivery-note p:last-child {
margin-bottom: 0;
}
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th,
.data-table td {
padding: var(--space-3) 10px;
text-align: left;
border-bottom: 1px solid var(--border-default);
}
.data-table th {
width: 160px;
color: var(--text-secondary);
}
.status-stack {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
}
.redeem-summary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 12px;
gap: var(--space-3);
margin-bottom: 14px;
}
.redeem-summary-card,
.redeem-attempt-card {
padding: 14px 16px;
border-radius: 16px;
border: 1px solid rgba(86,108,138,.1);
padding: 14px var(--space-4);
border-radius: var(--radius-md);
border: 1px solid var(--border-default);
background: rgba(248,250,252,.92);
}
.redeem-summary-card span,
.redeem-attempt-grid span {
display: block;
color: #64748b;
font-size: 12px;
color: var(--text-secondary);
font-size: var(--text-xs);
margin-bottom: 6px;
}
.redeem-summary-card strong,
.redeem-attempt-grid strong {
color: #1d3555;
color: var(--text-primary);
}
.redeem-attempts {
display: grid;
gap: 12px;
gap: var(--space-3);
}
.redeem-attempt-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
gap: var(--space-3);
}
.redeem-outcome {
color: #175cd3;
font-size: 13px;
color: var(--color-primary);
font-size: var(--text-sm);
font-weight: 600;
}
.redeem-attempt-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 12px;
margin-top: 12px;
gap: var(--space-3);
margin-top: var(--space-3);
}
.redeem-attempt-copy {
margin: 12px 0 0;
color: #334155;
margin: var(--space-3) 0 0;
color: var(--text-primary);
line-height: 1.6;
}
.table-empty { text-align: center; color: #64748b; }
.binding-primary { margin-top: 4px; color: #0f766e; font-size: 12px; font-weight: 600; }
.binding-meta { margin-top: 4px; color: #64748b; font-size: 12px; }
.manual-grid { display: grid; gap: 14px; }
.field-block { display: grid; gap: 8px; color: #1d3555; font-weight: 600; }
.manual-hint { margin: 14px 0 0; color: #64748b; }
.manual-summary { margin-top: 16px; padding: 14px; border-radius: 16px; background: rgba(248,250,252,.9); }
.manual-summary p { margin: 0 0 8px; color: #334155; }
.manual-summary p:last-child { margin-bottom: 0; }
.table-empty {
text-align: center;
color: var(--text-secondary);
}
.binding-primary {
margin-top: 4px;
color: var(--color-success);
font-size: var(--text-xs);
font-weight: 600;
}
.binding-meta {
margin-top: 4px;
color: var(--text-secondary);
font-size: var(--text-xs);
}
.manual-grid {
display: grid;
gap: 14px;
}
.field-block {
display: grid;
gap: var(--space-2);
color: var(--text-primary);
font-weight: 600;
}
.manual-hint {
margin: 14px 0 0;
color: var(--text-secondary);
}
.manual-summary {
margin-top: var(--space-4);
padding: 14px;
border-radius: var(--radius-md);
background: rgba(248,250,252,.9);
}
.manual-summary p {
margin: 0 0 var(--space-2);
color: var(--text-primary);
}
.manual-summary p:last-child {
margin-bottom: 0;
}
.flow-card {
display: grid;
gap: 16px;
gap: var(--space-4);
background:
radial-gradient(circle at top right, rgba(37, 99, 235, 0.08), transparent 28%),
linear-gradient(180deg, rgba(255,255,255,.96), rgba(248,250,252,.96));
}
.flow-card-head {
display: flex;
flex-wrap: wrap;
@@ -1269,151 +1390,194 @@ onBeforeUnmount(clearScreenshotPreview)
justify-content: space-between;
gap: 14px;
}
.flow-head-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.kuaishou-focus-grid,
.flow-grid {
.kuaishou-focus-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 14px;
}
.flow-block,
.flow-form-card {
padding: 16px;
.flow-block {
padding: var(--space-4);
border-radius: 18px;
border: 1px solid rgba(86,108,138,.1);
border: 1px solid var(--border-default);
background: rgba(248,250,252,.9);
}
.flow-block--spotlight {
background:
linear-gradient(160deg, rgba(255,255,255,.98), rgba(239,246,255,.95)),
rgba(248,250,252,.9);
}
.flow-block h4 {
margin: 0;
color: #1d3555;
color: var(--text-primary);
}
.flow-block p {
margin: 10px 0 0;
color: #64748b;
color: var(--text-secondary);
line-height: 1.6;
}
.flow-meta-list {
display: grid;
gap: 10px;
margin-top: 14px;
}
.flow-meta-list div {
display: grid;
gap: 4px;
}
.flow-meta-list span {
color: #64748b;
font-size: 12px;
color: var(--text-secondary);
font-size: var(--text-xs);
}
.flow-meta-list strong {
color: #1d3555;
color: var(--text-primary);
word-break: break-all;
}
.flow-meta-list--dense {
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
}
.flow-meta-list--two-column {
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.spotlight-head {
display: flex;
justify-content: space-between;
gap: 12px;
gap: var(--space-3);
align-items: flex-start;
}
.spotlight-actions {
display: flex;
align-items: center;
gap: 10px;
}
.flow-copy {
margin: 8px 0 0;
color: #64748b;
margin: var(--space-2) 0 0;
color: var(--text-secondary);
line-height: 1.6;
}
.flow-collapse {
display: grid;
gap: 12px;
gap: var(--space-3);
}
.checklist-grid {
display: grid;
gap: 12px;
margin-top: 16px;
gap: var(--space-3);
margin-top: var(--space-4);
}
.checklist-item {
padding: 14px 16px;
border-radius: 16px;
padding: 14px var(--space-4);
border-radius: var(--radius-md);
background: rgba(255,255,255,.84);
border: 1px solid rgba(86,108,138,.08);
border: 1px solid var(--border-default);
}
.checklist-item-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 8px;
gap: var(--space-3);
margin-bottom: var(--space-2);
}
.checklist-item-head span {
color: #64748b;
font-size: 12px;
color: var(--text-secondary);
font-size: var(--text-xs);
}
.checklist-item strong {
color: #0f172a;
color: var(--text-primary);
line-height: 1.5;
}
.role-hero {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 12px;
margin-top: 16px;
gap: var(--space-3);
margin-top: var(--space-4);
}
.role-hero div {
padding: 14px 16px;
border-radius: 16px;
padding: 14px var(--space-4);
border-radius: var(--radius-md);
background: rgba(255,255,255,.88);
border: 1px solid rgba(86,108,138,.08);
border: 1px solid var(--border-default);
}
.role-hero span {
display: block;
margin-bottom: 6px;
color: #64748b;
font-size: 12px;
color: var(--text-secondary);
font-size: var(--text-xs);
}
.role-hero strong {
color: #0f172a;
font-size: 18px;
color: var(--text-primary);
font-size: var(--text-lg);
line-height: 1.35;
word-break: break-word;
}
.role-warning {
margin: 14px 0 0;
padding: 12px 14px;
padding: var(--space-3) 14px;
border-radius: 14px;
background: rgba(255, 244, 229, 0.9);
background: var(--color-warning-light);
color: #b54708;
}
.event-copy { color: #334155; }
.event-time { margin-top: 6px; color: #64748b; font-size: 12px; }
.screenshot-link { display: inline-block; margin-bottom: 12px; color: #175cd3; text-decoration: none; }
.screenshot-empty { margin: 0 0 12px; color: #64748b; }
.event-copy {
color: var(--text-primary);
}
.event-time {
margin-top: 6px;
color: var(--text-secondary);
font-size: var(--text-xs);
}
.screenshot-link {
display: inline-block;
margin-bottom: var(--space-3);
color: var(--color-primary);
text-decoration: none;
}
.screenshot-empty {
margin: 0 0 var(--space-3);
color: var(--text-secondary);
}
.screenshot-preview {
display: block;
width: min(100%, 720px);
border-radius: 18px;
border: 1px solid rgba(86,108,138,.12);
border: 1px solid var(--border-default);
box-shadow: 0 20px 48px rgba(15,23,42,.08);
}
@media (max-width: 720px) {
@media (max-width: 900px) {
.hero-inline,
.hero-link-row,
.flow-card-head,
@@ -89,13 +89,12 @@ onMounted(loadDetail)
<template>
<section class="admin-panel">
<header class="panel-header">
<div>
<h1>Webhook 详情</h1>
<p>查看原始请求并手动重放处理流程</p>
</div>
<AdminPageHeader
title="Webhook 详情"
description="查看原始请求并手动重放处理流程。"
>
<el-button v-if="hasAdminRole('admin')" :loading="replayLoading" round type="primary" @click="submitReplay">重放处理</el-button>
</header>
</AdminPageHeader>
<p v-if="errorMessage" class="error-copy">{{ errorMessage }}</p>
<div v-if="loading" class="empty-block">Webhook 详情加载中</div>
@@ -172,7 +171,7 @@ onMounted(loadDetail)
<div v-if="detail.processError" class="inline-error">{{ detail.processError }}</div>
</section>
<section v-if="lastReplayResult" class="table-card">
<section v-if="lastReplayResult" class="info-card">
<h3>最近一次重放结果</h3>
<p class="result-copy">
{{ lastReplayResult.provider }} / {{ lastReplayResult.platform }}
@@ -183,40 +182,33 @@ onMounted(loadDetail)
· 事件 {{ 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>
<el-table :data="lastReplayResult.tasks" size="small">
<el-table-column prop="taskId" label="任务 ID" />
<el-table-column prop="taskNo" label="任务号" />
<el-table-column label="状态">
<template #default="{ row }">
<AdminStatusTag :status="row.status" />
</template>
</el-table-column>
</el-table>
</section>
<section class="table-card">
<section class="info-card">
<h3>解析后的 Payload</h3>
<pre class="json-block">{{ prettyJson(detail.payload) }}</pre>
</section>
<section class="table-card">
<section class="info-card">
<h3>Headers</h3>
<pre class="json-block">{{ prettyJson(detail.headers) }}</pre>
</section>
<section class="table-card">
<section class="info-card">
<h3>Query</h3>
<pre class="json-block">{{ prettyJson(detail.query) }}</pre>
</section>
<section class="table-card">
<section class="info-card">
<h3>Body</h3>
<pre class="json-block">{{ prettyJson(detail.body) }}</pre>
</section>
@@ -225,87 +217,132 @@ onMounted(loadDetail)
</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);
.admin-panel {
display: grid;
gap: var(--space-4);
}
.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; }
.info-card,
.empty-block,
.error-copy {
padding: 18px;
border-radius: var(--radius-lg);
background: var(--bg-surface);
border: 1px solid var(--border-default);
}
.overview-top {
display: flex;
justify-content: space-between;
gap: var(--space-4);
align-items: flex-start;
}
.overview-top h3,
.info-card h3 {
margin: 0;
color: var(--text-primary);
}
.muted-copy {
margin: var(--space-2) 0 0;
color: var(--text-secondary);
}
.tag-row {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
align-items: center;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
margin-top: 16px;
gap: var(--space-3);
margin-top: var(--space-4);
}
.summary-item {
display: grid;
gap: 6px;
padding: 14px;
border-radius: 16px;
background: #f8fafc;
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: var(--radius-md);
background: var(--bg-page);
border: 1px solid var(--border-default);
}
.summary-label {
color: #64748b;
font-size: 12px;
color: var(--text-secondary);
font-size: var(--text-xs);
font-weight: 700;
}
.summary-item strong {
color: #0f172a;
color: var(--text-primary);
word-break: break-all;
}
.summary-note {
color: #64748b;
font-size: 12px;
color: var(--text-secondary);
font-size: var(--text-xs);
line-height: 1.5;
word-break: break-all;
}
.event-key-block {
display: grid;
gap: 8px;
margin-top: 16px;
gap: var(--space-2);
margin-top: var(--space-4);
padding: 14px;
border-radius: 16px;
background: #f8fafc;
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: var(--radius-md);
background: var(--bg-page);
border: 1px solid var(--border-default);
}
.event-key-block code {
color: #334155;
font-size: 12px;
color: var(--text-primary);
font-size: var(--text-xs);
line-height: 1.6;
word-break: break-all;
white-space: pre-wrap;
}
.error-copy { color: #b42318; }
.error-copy {
color: var(--color-danger);
}
.inline-error {
margin-top: 16px;
padding: 12px 14px;
margin-top: var(--space-4);
padding: var(--space-3) 14px;
border-radius: 14px;
color: #b42318;
background: #fef3f2;
color: var(--color-danger);
background: var(--color-danger-light);
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; }
.result-copy {
margin: var(--space-3) 0;
color: var(--text-secondary);
}
.inline-link {
color: var(--color-primary);
}
.json-block {
margin: 12px 0 0;
margin: var(--space-3) 0 0;
padding: 14px;
border-radius: 14px;
background: #0f172a;
color: #e2e8f0;
overflow: auto;
font-size: 12px;
font-size: var(--text-xs);
line-height: 1.6;
}
@media (max-width: 960px) {
@media (max-width: 900px) {
.overview-top {
flex-direction: column;
}