履约配置和 库存界面ui优化
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
|
||||
|
||||
import { showSuccess } from '@/lib/feedback'
|
||||
import {
|
||||
@@ -83,6 +83,22 @@ const lookupForm = reactive({
|
||||
platformOrderId: '',
|
||||
})
|
||||
|
||||
const ruleMetrics = computed(() => {
|
||||
const completedCount = bindings.value.filter(isEditableBindingComplete).length
|
||||
const manualCount = bindings.value.filter((item) => item.profileKey.trim() === 'manual_review').length
|
||||
const assistedCount = bindings.value.filter((item) => item.profileKey.trim() === 'tencent_claim_assisted').length
|
||||
|
||||
return {
|
||||
totalCount: bindings.value.length,
|
||||
completedCount,
|
||||
draftCount: Math.max(bindings.value.length - completedCount, 0),
|
||||
manualCount,
|
||||
assistedCount,
|
||||
}
|
||||
})
|
||||
|
||||
const pendingObservedCount = computed(() => observedProducts.value.filter((item) => !item.configured).length)
|
||||
|
||||
type ImportableProductCandidate = Pick<
|
||||
AdminObservedProductItem,
|
||||
'provider' | 'platform' | 'shopId' | 'externalSkuCode' | 'externalItemId' | 'externalSkuName'
|
||||
@@ -449,56 +465,56 @@ onMounted(loadConfigs)
|
||||
<template>
|
||||
<section class="admin-panel">
|
||||
<header class="panel-header">
|
||||
<div>
|
||||
<div class="panel-header-copy">
|
||||
<h1>履约配置</h1>
|
||||
<p>把外部商品识别成内部履约 SKU,再决定绑定库存还是转人工。保存后立即同步到后端规则表。</p>
|
||||
<p>把外部商品统一映射到内部履约 SKU,并指定最终走自动、半自动还是人工处理。</p>
|
||||
</div>
|
||||
<div class="header-tags">
|
||||
<span class="header-tag">外部条件命中 1 个即可</span>
|
||||
<span class="header-tag">同类商品尽量复用同一内部 SKU</span>
|
||||
<span class="header-tag">保存后立即生效</span>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<el-button round @click="loadConfigs">刷新</el-button>
|
||||
<el-button round type="primary" @click="addBinding">新增规则</el-button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div v-if="!hasAdminRole('admin')" class="empty-block">仅管理员可以维护履约配置。</div>
|
||||
|
||||
<template v-else>
|
||||
<section class="meta-card">
|
||||
<div class="meta-line">
|
||||
<span class="meta-label">配置文件</span>
|
||||
<section class="overview-card">
|
||||
<div class="overview-file">
|
||||
<span class="overview-label">配置文件</span>
|
||||
<code>{{ filePath || '-' }}</code>
|
||||
</div>
|
||||
<div class="meta-line">
|
||||
<span class="meta-label">说明</span>
|
||||
<span>每条规则同时定义“外部商品匹配条件”和“内部履约 SKU / 履约方式”。未命中的商品会直接忽略,不再继续走后续履约动作。</span>
|
||||
|
||||
<div class="overview-stats">
|
||||
<article class="overview-stat">
|
||||
<span>规则总数</span>
|
||||
<strong>{{ ruleMetrics.totalCount }}</strong>
|
||||
</article>
|
||||
<article class="overview-stat">
|
||||
<span>完整规则</span>
|
||||
<strong>{{ ruleMetrics.completedCount }}</strong>
|
||||
</article>
|
||||
<article class="overview-stat">
|
||||
<span>待完善</span>
|
||||
<strong>{{ ruleMetrics.draftCount }}</strong>
|
||||
</article>
|
||||
<article class="overview-stat">
|
||||
<span>待补商品</span>
|
||||
<strong>{{ pendingObservedCount }}</strong>
|
||||
</article>
|
||||
<article class="overview-stat">
|
||||
<span>已识别商品</span>
|
||||
<strong>{{ observedProducts.length }}</strong>
|
||||
</article>
|
||||
</div>
|
||||
<div class="meta-line">
|
||||
<span class="meta-label">字段怎么理解</span>
|
||||
<div class="hint-grid">
|
||||
<div class="hint-item">
|
||||
<strong>外部 SKU 编码 / 外部商品 ID / 外部商品名</strong>
|
||||
<span>这是平台或店铺回传的原始商品标识,用来识别你卖的是哪一种商品。</span>
|
||||
</div>
|
||||
<div class="hint-item">
|
||||
<strong>内部履约 SKU</strong>
|
||||
<span>这是你系统里的统一商品编码,后续库存绑定、自动履约、人工处理都靠它继续往下走。</span>
|
||||
</div>
|
||||
<div class="hint-item">
|
||||
<strong>内部商品名</strong>
|
||||
<span>主要给后台展示看,建议填你们内部真正认识的商品名。</span>
|
||||
</div>
|
||||
<div class="hint-item">
|
||||
<strong>履约方式</strong>
|
||||
<span>`腾讯领取兑换` 表示用户确认后自动兑换,`腾讯领取兑换(半自动+人工)` 表示用户先登录、客服确认角色后再自动兑换,`人工发货` 表示直接落到人工处理。</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="meta-line">
|
||||
<span class="meta-label">内部履约 SKU 怎么填</span>
|
||||
<div class="sku-guide">
|
||||
<span>推荐用你们内部统一命名,不要直接抄店铺里的外部 SKU。</span>
|
||||
<span>建议格式:`游戏或业务_商品类型_规格`,例如 `delta_havabi_500`、`dnf_cdk_blackdiamond`、`manual_peripheral_common`。</span>
|
||||
<span>原则是“同一种履约商品,无论来自哪个平台、哪个店铺,都尽量落到同一个内部履约 SKU”。</span>
|
||||
</div>
|
||||
|
||||
<div class="overview-notes">
|
||||
<span class="overview-note">外部 SKU / 商品 ID / 商品名命中任一即可</span>
|
||||
<span class="overview-note">内部 SKU 决定库存绑定与履约链路</span>
|
||||
<span class="overview-note">同类商品跨平台尽量共用同一内部 SKU</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -506,126 +522,24 @@ onMounted(loadConfigs)
|
||||
<div v-if="loading" class="empty-block">履约配置加载中</div>
|
||||
|
||||
<template v-else>
|
||||
<section class="table-card">
|
||||
<div class="section-title-row">
|
||||
<div>
|
||||
<h3>手动查询订单商品</h3>
|
||||
<p>新商品还没配规则时,可以按店铺 ID 和平台订单号临时补查,不需要恢复全量订单详情补查。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lookup-grid">
|
||||
<label class="field-block">
|
||||
<span>渠道</span>
|
||||
<input class="text-input" value="agiso" disabled />
|
||||
</label>
|
||||
|
||||
<label class="field-block">
|
||||
<span>平台</span>
|
||||
<input class="text-input" value="xianyu" disabled />
|
||||
</label>
|
||||
|
||||
<label class="field-block">
|
||||
<span>店铺 ID</span>
|
||||
<input
|
||||
v-model="lookupForm.shopId"
|
||||
class="text-input"
|
||||
placeholder="例如 252609"
|
||||
@keydown.enter.prevent="lookupOrderProducts"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="field-block">
|
||||
<span>平台订单号</span>
|
||||
<input
|
||||
v-model="lookupForm.platformOrderId"
|
||||
class="text-input"
|
||||
placeholder="输入需要补查的订单号"
|
||||
@keydown.enter.prevent="lookupOrderProducts"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="lookup-actions">
|
||||
<el-button :loading="lookupLoading" round type="primary" @click="lookupOrderProducts">查询订单商品</el-button>
|
||||
<span class="cell-subtle">当前仅支持 Agiso 咸鱼订单。查询结果只用于补配置,不会写入订单库。</span>
|
||||
</div>
|
||||
|
||||
<p v-if="lookupErrorMessage" class="error-copy lookup-error">{{ lookupErrorMessage }}</p>
|
||||
<div v-else-if="lookupLoading" class="empty-inline">正在查询订单详情…</div>
|
||||
|
||||
<template v-else-if="lookupResult">
|
||||
<div class="lookup-summary">
|
||||
<div class="lookup-summary-item">
|
||||
<span class="meta-label">订单</span>
|
||||
<strong>{{ lookupResult.order.platformOrderId }}</strong>
|
||||
<span class="cell-subtle">{{ lookupResult.order.shopName || lookupResult.order.shopId || '-' }}</span>
|
||||
</div>
|
||||
<div class="lookup-summary-item">
|
||||
<span class="meta-label">买家</span>
|
||||
<strong>{{ lookupResult.order.buyerName || '-' }}</strong>
|
||||
<span class="cell-subtle">支付时间:{{ formatAdminDateTime(lookupResult.order.paidAt) }}</span>
|
||||
</div>
|
||||
<div class="lookup-summary-item">
|
||||
<span class="meta-label">金额</span>
|
||||
<strong>{{ lookupResult.order.totalAmount || '0.00' }}</strong>
|
||||
<span class="cell-subtle">补查结果:{{ lookupResult.order.enriched ? '已补全' : lookupResult.order.enrichReason || '已返回' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>外部商品</th>
|
||||
<th>数量</th>
|
||||
<th>识别结果</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in lookupResult.items" :key="item.lineId">
|
||||
<td>
|
||||
<div class="cell-stack">
|
||||
<strong>{{ item.externalSkuName || item.itemTitle || item.externalSkuCode || item.externalItemId || '-' }}</strong>
|
||||
<span class="cell-subtle">SKU: {{ item.externalSkuCode || '-' }}</span>
|
||||
<span class="cell-subtle">ItemId: {{ item.externalItemId || '-' }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ item.quantity }}</td>
|
||||
<td>
|
||||
<div class="cell-stack">
|
||||
<strong>{{ item.configured ? '已配置' : '未配置' }}</strong>
|
||||
<span v-if="item.matchedBinding" class="cell-subtle">
|
||||
{{ item.matchedBinding.skuName || item.matchedBinding.skuCode || '-' }} / {{ item.matchedBinding.profileKey || '-' }}
|
||||
</span>
|
||||
<span v-else class="cell-subtle">可直接导入成新规则草稿</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<el-button
|
||||
v-if="!item.configured && !isLookupProductImported(item.lineId)"
|
||||
link
|
||||
type="primary"
|
||||
@click="importLookupProduct(item)"
|
||||
>
|
||||
导入到规则
|
||||
</el-button>
|
||||
<span v-else-if="item.configured" class="cell-subtle">已存在</span>
|
||||
<span v-else class="cell-subtle">已导入草稿</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<section class="table-card">
|
||||
<div class="section-title-row">
|
||||
<section class="table-card table-card--dense">
|
||||
<div class="section-title-row section-title-row--tight">
|
||||
<div>
|
||||
<h3>已配置规则</h3>
|
||||
<p>建议至少填写平台、店铺、外部 SKU 编码或外部商品名,再指定内部履约 SKU 和履约档案。</p>
|
||||
<p>正式规则集中维护在这里,新补查到的商品也会导入到这个区域继续补全。</p>
|
||||
</div>
|
||||
<div class="section-header-tools">
|
||||
<div class="section-mini-stats">
|
||||
<span class="mini-stat-chip">完整 {{ ruleMetrics.completedCount }}</span>
|
||||
<span class="mini-stat-chip">待完善 {{ ruleMetrics.draftCount }}</span>
|
||||
<span class="mini-stat-chip">人工 {{ ruleMetrics.manualCount }}</span>
|
||||
<span class="mini-stat-chip">半自动 {{ ruleMetrics.assistedCount }}</span>
|
||||
</div>
|
||||
<div class="section-action-group">
|
||||
<el-button round @click="addBinding">新增规则</el-button>
|
||||
<el-button :loading="saving" round type="primary" @click="saveConfigs">保存配置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-button :loading="saving" round type="primary" @click="saveConfigs">保存配置</el-button>
|
||||
</div>
|
||||
|
||||
<p v-if="validationState?.message" class="validation-banner">{{ validationState.message }}</p>
|
||||
@@ -767,11 +681,131 @@ onMounted(loadConfigs)
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="table-card">
|
||||
<div class="section-title-row">
|
||||
<section class="table-card table-card--dense">
|
||||
<div class="section-title-row section-title-row--tight">
|
||||
<div>
|
||||
<h3>手动补查订单商品</h3>
|
||||
<p>新商品还没配规则时,可以按店铺 ID 和平台订单号临时补查,查完直接导入规则草稿。</p>
|
||||
</div>
|
||||
<span class="section-note-chip">仅支持 Agiso 咸鱼订单</span>
|
||||
</div>
|
||||
|
||||
<div class="lookup-toolbar">
|
||||
<label class="field-block">
|
||||
<span>渠道</span>
|
||||
<input class="text-input" value="agiso" disabled />
|
||||
</label>
|
||||
|
||||
<label class="field-block">
|
||||
<span>平台</span>
|
||||
<input class="text-input" value="xianyu" disabled />
|
||||
</label>
|
||||
|
||||
<label class="field-block">
|
||||
<span>店铺 ID</span>
|
||||
<input
|
||||
v-model="lookupForm.shopId"
|
||||
class="text-input"
|
||||
placeholder="例如 252609"
|
||||
@keydown.enter.prevent="lookupOrderProducts"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="field-block">
|
||||
<span>平台订单号</span>
|
||||
<input
|
||||
v-model="lookupForm.platformOrderId"
|
||||
class="text-input"
|
||||
placeholder="输入需要补查的订单号"
|
||||
@keydown.enter.prevent="lookupOrderProducts"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div class="field-block field-block--action">
|
||||
<span>操作</span>
|
||||
<el-button :loading="lookupLoading" round type="primary" @click="lookupOrderProducts">查询订单商品</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="toolbar-hint">查询结果只用于补配置,不会写入订单库,也不会影响现有订单数据。</p>
|
||||
|
||||
<p v-if="lookupErrorMessage" class="error-copy lookup-error">{{ lookupErrorMessage }}</p>
|
||||
<div v-else-if="lookupLoading" class="empty-inline">正在查询订单详情…</div>
|
||||
|
||||
<template v-else-if="lookupResult">
|
||||
<div class="lookup-summary">
|
||||
<div class="lookup-summary-item">
|
||||
<span class="summary-label">订单</span>
|
||||
<strong>{{ lookupResult.order.platformOrderId }}</strong>
|
||||
<span class="cell-subtle">{{ lookupResult.order.shopName || lookupResult.order.shopId || '-' }}</span>
|
||||
</div>
|
||||
<div class="lookup-summary-item">
|
||||
<span class="summary-label">买家</span>
|
||||
<strong>{{ lookupResult.order.buyerName || '-' }}</strong>
|
||||
<span class="cell-subtle">支付时间:{{ formatAdminDateTime(lookupResult.order.paidAt) }}</span>
|
||||
</div>
|
||||
<div class="lookup-summary-item">
|
||||
<span class="summary-label">金额</span>
|
||||
<strong>{{ lookupResult.order.totalAmount || '0.00' }}</strong>
|
||||
<span class="cell-subtle">补查结果:{{ lookupResult.order.enriched ? '已补全' : lookupResult.order.enrichReason || '已返回' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>外部商品</th>
|
||||
<th>数量</th>
|
||||
<th>识别结果</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in lookupResult.items" :key="item.lineId">
|
||||
<td>
|
||||
<div class="cell-stack">
|
||||
<strong>{{ item.externalSkuName || item.itemTitle || item.externalSkuCode || item.externalItemId || '-' }}</strong>
|
||||
<span class="cell-subtle">SKU: {{ item.externalSkuCode || '-' }}</span>
|
||||
<span class="cell-subtle">ItemId: {{ item.externalItemId || '-' }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ item.quantity }}</td>
|
||||
<td>
|
||||
<div class="cell-stack">
|
||||
<strong>{{ item.configured ? '已配置' : '未配置' }}</strong>
|
||||
<span v-if="item.matchedBinding" class="cell-subtle">
|
||||
{{ item.matchedBinding.skuName || item.matchedBinding.skuCode || '-' }} / {{ item.matchedBinding.profileKey || '-' }}
|
||||
</span>
|
||||
<span v-else class="cell-subtle">可直接导入成新规则草稿</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<el-button
|
||||
v-if="!item.configured && !isLookupProductImported(item.lineId)"
|
||||
link
|
||||
type="primary"
|
||||
@click="importLookupProduct(item)"
|
||||
>
|
||||
导入到规则
|
||||
</el-button>
|
||||
<span v-else-if="item.configured" class="cell-subtle">已存在</span>
|
||||
<span v-else class="cell-subtle">已导入草稿</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<section class="table-card table-card--dense">
|
||||
<div class="section-title-row section-title-row--tight">
|
||||
<div>
|
||||
<h3>近期识别到的外部商品</h3>
|
||||
<p>从已入库订单里提取的外部商品信息,可一键导入规则后补充内部履约 SKU。</p>
|
||||
<p>从已入库订单里提取的外部商品信息,优先处理未配置商品,导入后再补齐内部 SKU。</p>
|
||||
</div>
|
||||
<div class="section-mini-stats">
|
||||
<span class="mini-stat-chip">待补 {{ pendingObservedCount }}</span>
|
||||
<span class="mini-stat-chip">总计 {{ observedProducts.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -830,7 +864,7 @@ onMounted(loadConfigs)
|
||||
<style scoped>
|
||||
.admin-panel {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.panel-header,
|
||||
@@ -849,92 +883,178 @@ onMounted(loadConfigs)
|
||||
|
||||
.panel-header p,
|
||||
.section-title-row p {
|
||||
margin: 8px 0 0;
|
||||
margin: 6px 0 0;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.panel-header-copy {
|
||||
min-width: 280px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.header-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-tag,
|
||||
.section-note-chip,
|
||||
.mini-stat-chip,
|
||||
.summary-label,
|
||||
.overview-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(37, 89, 167, 0.08);
|
||||
border: 1px solid rgba(37, 89, 167, 0.12);
|
||||
color: #2559a7;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.meta-card,
|
||||
.overview-card,
|
||||
.table-card,
|
||||
.empty-block,
|
||||
.error-copy {
|
||||
padding: 18px;
|
||||
padding: 16px;
|
||||
border-radius: 20px;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
border: 1px solid rgba(86, 108, 138, 0.1);
|
||||
}
|
||||
|
||||
.meta-card {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
.table-card--dense {
|
||||
padding: 14px 16px 16px;
|
||||
}
|
||||
|
||||
.hint-grid,
|
||||
.sku-guide {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.lookup-grid,
|
||||
.lookup-summary {
|
||||
.overview-card {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.hint-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.lookup-grid,
|
||||
.lookup-summary {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.hint-item {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 14px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid rgba(148, 163, 184, 0.14);
|
||||
}
|
||||
|
||||
.hint-item strong {
|
||||
color: #1d3555;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.hint-item span,
|
||||
.sku-guide span {
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.meta-line {
|
||||
.overview-file {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.lookup-actions {
|
||||
margin-top: 14px;
|
||||
.overview-file code {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 8px 12px;
|
||||
border-radius: 14px;
|
||||
background: rgba(15, 23, 42, 0.05);
|
||||
color: #1d3555;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.overview-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.overview-stat {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid rgba(148, 163, 184, 0.14);
|
||||
}
|
||||
|
||||
.overview-stat span {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.overview-stat strong {
|
||||
color: #1d3555;
|
||||
font-size: 22px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.overview-notes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.overview-note {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 32px;
|
||||
padding: 0 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(15, 23, 42, 0.04);
|
||||
color: #475467;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.section-title-row--tight {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-header-tools {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-mini-stats,
|
||||
.section-action-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.lookup-toolbar,
|
||||
.lookup-summary {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.lookup-toolbar {
|
||||
grid-template-columns: 96px 110px minmax(140px, 1fr) minmax(180px, 1.2fr) 136px;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.field-block--action :deep(.el-button) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.toolbar-hint {
|
||||
margin: 10px 0 0;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.lookup-summary {
|
||||
margin-top: 16px;
|
||||
margin-top: 14px;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.lookup-summary-item {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
gap: 6px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
background: #f8fafc;
|
||||
@@ -945,16 +1065,10 @@ onMounted(loadConfigs)
|
||||
color: #1d3555;
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.binding-card {
|
||||
margin-top: 14px;
|
||||
padding: 16px;
|
||||
border-radius: 18px;
|
||||
margin-top: 12px;
|
||||
padding: 14px;
|
||||
border-radius: 16px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||
}
|
||||
@@ -967,9 +1081,9 @@ onMounted(loadConfigs)
|
||||
.binding-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.binding-summary {
|
||||
@@ -1011,7 +1125,7 @@ onMounted(loadConfigs)
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
color: #1d3555;
|
||||
border-radius: 999px;
|
||||
padding: 8px 14px;
|
||||
padding: 7px 12px;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -1026,12 +1140,12 @@ onMounted(loadConfigs)
|
||||
.binding-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
gap: 10px 12px;
|
||||
}
|
||||
|
||||
.validation-banner {
|
||||
margin: 14px 0 0;
|
||||
padding: 12px 14px;
|
||||
margin: 12px 0 0;
|
||||
padding: 10px 12px;
|
||||
border-radius: 14px;
|
||||
background: #fff4f2;
|
||||
border: 1px solid rgba(244, 63, 94, 0.18);
|
||||
@@ -1041,7 +1155,7 @@ onMounted(loadConfigs)
|
||||
|
||||
.field-block {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.field-block span {
|
||||
@@ -1062,8 +1176,8 @@ onMounted(loadConfigs)
|
||||
|
||||
.text-input {
|
||||
width: 100%;
|
||||
min-height: 42px;
|
||||
padding: 0 14px;
|
||||
min-height: 40px;
|
||||
padding: 0 12px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(86, 108, 138, 0.18);
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
@@ -1081,14 +1195,14 @@ onMounted(loadConfigs)
|
||||
}
|
||||
|
||||
.binding-inline-error {
|
||||
margin: 12px 0 0;
|
||||
margin: 10px 0 0;
|
||||
color: #b42318;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.binding-actions {
|
||||
margin-top: 12px;
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
@@ -1144,15 +1258,15 @@ onMounted(loadConfigs)
|
||||
@media (max-width: 980px) {
|
||||
.panel-header,
|
||||
.section-title-row,
|
||||
.lookup-actions,
|
||||
.section-header-tools,
|
||||
.binding-actions,
|
||||
.binding-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.hint-grid,
|
||||
.lookup-grid,
|
||||
.overview-stats,
|
||||
.lookup-toolbar,
|
||||
.lookup-summary,
|
||||
.binding-grid {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -461,35 +461,22 @@ onMounted(async () => {
|
||||
<template>
|
||||
<section class="admin-panel">
|
||||
<header class="panel-header">
|
||||
<div>
|
||||
<div class="panel-header-copy">
|
||||
<h1>库存管理</h1>
|
||||
<p>库存项现在按“内部履约 SKU + 凭据类型”入池。当前三角洲行动腾讯兑换码自动领取,会优先使用 `tencent_code` 类型库存。</p>
|
||||
<p>按内部履约 SKU 和凭据类型管理库存、预占和发放。</p>
|
||||
</div>
|
||||
<div class="header-tags">
|
||||
<span class="header-tag">按 SKU + 凭据类型入池</span>
|
||||
<span class="header-tag">腾讯兑换优先使用 `tencent_code`</span>
|
||||
<span class="header-tag">命中库存后自动流转或转人工</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="guide-card">
|
||||
<article class="guide-item">
|
||||
<span class="guide-label">1. 商品识别</span>
|
||||
<h2>先识别成内部履约 SKU</h2>
|
||||
<p>平台商品不会直接配库存。订单先在“商品识别与履约配置”里映射成内部履约 SKU,再来到这里找库存。</p>
|
||||
</article>
|
||||
<article class="guide-item">
|
||||
<span class="guide-label">2. 库存预占</span>
|
||||
<h2>再按凭据类型找库存项</h2>
|
||||
<p>`tencent_claim_redeem` 和 `tencent_claim_assisted` 当前都会优先查找 `tencent_code`,适合三角洲行动这类腾讯兑换码领取场景。以后扩展别的履约档案时,也会按各自 requirement 匹配。</p>
|
||||
</article>
|
||||
<article class="guide-item">
|
||||
<span class="guide-label">3. 履约结果</span>
|
||||
<h2>自动绑定或转人工</h2>
|
||||
<p>命中库存后任务会进入预占 / 发放链路;配置为 `tencent_claim_assisted` 时会进入“用户登录 + 客服确认 + 后端自动兑换”;配置走 `manual_review` 时,会落到人工处理。</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="filter-card">
|
||||
<div class="card-head">
|
||||
<div>
|
||||
<h2>查询库存池</h2>
|
||||
<p>按内部履约 SKU、凭据类型、批次和状态查看当前库存池。</p>
|
||||
<p>按内部履约 SKU、凭据类型、批次和状态快速定位库存。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
@@ -516,7 +503,7 @@ onMounted(async () => {
|
||||
<div class="card-head">
|
||||
<div>
|
||||
<h2>入库与导入</h2>
|
||||
<p>这里填写的是内部履约 SKU,不是平台侧 `sku_code`。内部 SKU 支持中文命名;同一类商品建议长期复用同一个名字,这样订单才能稳定共用库存池。</p>
|
||||
<p>填写内部履约 SKU,不是平台侧 `sku_code`。建议同类商品长期复用同一个内部名称。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -770,26 +757,24 @@ onMounted(async () => {
|
||||
<style scoped>
|
||||
.admin-panel {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.panel-header h1,
|
||||
.card-head h2,
|
||||
.guide-item h2 {
|
||||
.pool-title {
|
||||
margin: 0;
|
||||
color: #1d3555;
|
||||
}
|
||||
|
||||
.panel-header p,
|
||||
.card-head p,
|
||||
.guide-item p,
|
||||
.toolbar-copy,
|
||||
.section-tip {
|
||||
margin: 8px 0 0;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.guide-card,
|
||||
.filter-card,
|
||||
.import-card,
|
||||
.table-card,
|
||||
@@ -801,33 +786,38 @@ onMounted(async () => {
|
||||
border: 1px solid rgba(86, 108, 138, 0.1);
|
||||
}
|
||||
|
||||
.guide-card {
|
||||
.panel-header {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.guide-item {
|
||||
padding: 16px;
|
||||
border-radius: 18px;
|
||||
background: linear-gradient(180deg, rgba(244, 248, 255, 0.96), rgba(255, 255, 255, 0.96));
|
||||
border: 1px solid rgba(86, 108, 138, 0.08);
|
||||
.panel-header-copy p {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.guide-label,
|
||||
.helper-title {
|
||||
.header-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.header-tag,
|
||||
.helper-title,
|
||||
.summary-label,
|
||||
.pool-total {
|
||||
display: inline-flex;
|
||||
margin-bottom: 10px;
|
||||
align-items: center;
|
||||
min-height: 32px;
|
||||
padding: 0 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(37, 89, 167, 0.08);
|
||||
border: 1px solid rgba(37, 89, 167, 0.12);
|
||||
color: #2559a7;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.guide-item p {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.card-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -975,14 +965,6 @@ onMounted(async () => {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.summary-label,
|
||||
.pool-total {
|
||||
color: #2559a7;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
font-size: 30px;
|
||||
line-height: 1;
|
||||
@@ -1176,7 +1158,6 @@ onMounted(async () => {
|
||||
@media (max-width: 1180px) {
|
||||
.summary-grid,
|
||||
.pool-grid,
|
||||
.guide-card,
|
||||
.form-grid,
|
||||
.helper-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@@ -1186,12 +1167,12 @@ onMounted(async () => {
|
||||
@media (max-width: 860px) {
|
||||
.summary-grid,
|
||||
.pool-grid,
|
||||
.guide-card,
|
||||
.form-grid,
|
||||
.helper-grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.header-tags,
|
||||
.single-row,
|
||||
.action-row,
|
||||
.sku-helper-bar {
|
||||
|
||||
Reference in New Issue
Block a user