ui优化-2
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user