增加 履约配置 手动查询
This commit is contained in:
@@ -3,6 +3,7 @@ import type {
|
||||
AdminAgisoObservedShopItem,
|
||||
AdminAgisoShopConfigItem,
|
||||
AdminAuditLogItem,
|
||||
AdminFulfillmentLookupResult,
|
||||
AdminFulfillmentBindingConfigItem,
|
||||
AdminInventoryItemListItem,
|
||||
AdminMessageDeliveryListItem,
|
||||
@@ -85,6 +86,15 @@ export function fetchAdminFulfillmentBindingConfigs() {
|
||||
}>('/api/v1/admin/platform-config/fulfillment-bindings')
|
||||
}
|
||||
|
||||
export function lookupAdminFulfillmentBindingOrder(payload: {
|
||||
provider?: string
|
||||
platform?: string
|
||||
shopId: string
|
||||
platformOrderId: string
|
||||
}) {
|
||||
return apiPost<AdminFulfillmentLookupResult>('/api/v1/admin/platform-config/fulfillment-bindings/lookup-order', payload)
|
||||
}
|
||||
|
||||
export function saveAdminFulfillmentBindingConfigs(payload: { bindings: Array<Record<string, unknown>> }) {
|
||||
return apiPost<{
|
||||
filePath: string
|
||||
|
||||
@@ -98,6 +98,37 @@ export interface AdminObservedProductItem {
|
||||
latestSeenAt: string | null
|
||||
orderItemCount: number
|
||||
configured: boolean
|
||||
matchedBinding: null | {
|
||||
skuCode: string
|
||||
skuName: string
|
||||
profileKey: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface AdminFulfillmentLookupOrder {
|
||||
provider: string
|
||||
platform: string
|
||||
shopId: string
|
||||
shopName: string
|
||||
platformOrderId: string
|
||||
buyerName: string
|
||||
totalAmount: string
|
||||
totalAmountFen: number
|
||||
paidAt: string | null
|
||||
enriched: boolean
|
||||
enrichReason: string
|
||||
errorMessage: string
|
||||
}
|
||||
|
||||
export interface AdminFulfillmentLookupItem extends AdminObservedProductItem {
|
||||
lineId: string
|
||||
itemTitle: string
|
||||
quantity: number
|
||||
}
|
||||
|
||||
export interface AdminFulfillmentLookupResult {
|
||||
order: AdminFulfillmentLookupOrder
|
||||
items: AdminFulfillmentLookupItem[]
|
||||
}
|
||||
|
||||
export interface AdminDashboardSummary {
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onMounted, ref } from 'vue'
|
||||
import { nextTick, onMounted, reactive, ref } from 'vue'
|
||||
|
||||
import { showSuccess } from '@/lib/feedback'
|
||||
import {
|
||||
fetchAdminFulfillmentBindingConfigs,
|
||||
lookupAdminFulfillmentBindingOrder,
|
||||
saveAdminFulfillmentBindingConfigs,
|
||||
} from '@/services/admin'
|
||||
import type { AdminFulfillmentBindingConfigItem, AdminObservedProductItem } from '@/types/admin'
|
||||
import type {
|
||||
AdminFulfillmentBindingConfigItem,
|
||||
AdminFulfillmentLookupItem,
|
||||
AdminFulfillmentLookupResult,
|
||||
AdminObservedProductItem,
|
||||
} from '@/types/admin'
|
||||
import { hasAdminRole } from '@/utils/admin-auth'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
|
||||
@@ -65,9 +71,22 @@ const errorMessage = ref('')
|
||||
const filePath = ref('')
|
||||
const bindings = ref<EditableBinding[]>([])
|
||||
const observedProducts = ref<AdminObservedProductItem[]>([])
|
||||
const lookupLoading = ref(false)
|
||||
const lookupErrorMessage = ref('')
|
||||
const lookupResult = ref<AdminFulfillmentLookupResult | null>(null)
|
||||
const importedLookupLineIds = ref<string[]>([])
|
||||
const VALID_PROFILE_KEYS = new Set(PROFILE_OPTIONS.map((item) => item.value))
|
||||
const validationState = ref<ValidationState>(null)
|
||||
const collapsedBindingIds = ref<string[]>([])
|
||||
const lookupForm = reactive({
|
||||
shopId: '',
|
||||
platformOrderId: '',
|
||||
})
|
||||
|
||||
type ImportableProductCandidate = Pick<
|
||||
AdminObservedProductItem,
|
||||
'provider' | 'platform' | 'shopId' | 'externalSkuCode' | 'externalItemId' | 'externalSkuName'
|
||||
>
|
||||
|
||||
function createEmptyBinding(): EditableBinding {
|
||||
return {
|
||||
@@ -198,9 +217,8 @@ function removeBinding(id: string) {
|
||||
bindings.value = bindings.value.filter((item) => item.id !== id)
|
||||
}
|
||||
|
||||
function importObservedProduct(item: AdminObservedProductItem) {
|
||||
validationState.value = null
|
||||
const nextBinding = {
|
||||
function createBindingFromProductCandidate(item: ImportableProductCandidate): EditableBinding {
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
provider: item.provider || 'agiso',
|
||||
platform: item.platform,
|
||||
@@ -215,10 +233,26 @@ function importObservedProduct(item: AdminObservedProductItem) {
|
||||
externalSkuName: item.externalSkuName,
|
||||
resolvedSkuName: item.externalSkuName,
|
||||
}
|
||||
}
|
||||
|
||||
function importObservedProduct(item: ImportableProductCandidate) {
|
||||
validationState.value = null
|
||||
const nextBinding = createBindingFromProductCandidate(item)
|
||||
setBindingCollapsed(nextBinding.id, false)
|
||||
bindings.value.unshift(nextBinding)
|
||||
}
|
||||
|
||||
function importLookupProduct(item: AdminFulfillmentLookupItem) {
|
||||
importObservedProduct(item)
|
||||
if (!importedLookupLineIds.value.includes(item.lineId)) {
|
||||
importedLookupLineIds.value = [...importedLookupLineIds.value, item.lineId]
|
||||
}
|
||||
}
|
||||
|
||||
function isLookupProductImported(lineId: string) {
|
||||
return importedLookupLineIds.value.includes(lineId)
|
||||
}
|
||||
|
||||
function normalizeBindingForSave(item: EditableBinding): SaveBindingPayload {
|
||||
return {
|
||||
provider: item.provider.trim() || 'agiso',
|
||||
@@ -380,6 +414,35 @@ async function saveConfigs() {
|
||||
}
|
||||
}
|
||||
|
||||
async function lookupOrderProducts() {
|
||||
const shopId = lookupForm.shopId.trim()
|
||||
const platformOrderId = lookupForm.platformOrderId.trim()
|
||||
|
||||
if (!shopId || !platformOrderId) {
|
||||
lookupErrorMessage.value = '请先填写店铺 ID 和平台订单号'
|
||||
return
|
||||
}
|
||||
|
||||
lookupLoading.value = true
|
||||
lookupErrorMessage.value = ''
|
||||
|
||||
try {
|
||||
const response = await lookupAdminFulfillmentBindingOrder({
|
||||
provider: 'agiso',
|
||||
platform: 'xianyu',
|
||||
shopId,
|
||||
platformOrderId,
|
||||
})
|
||||
lookupResult.value = response.data
|
||||
importedLookupLineIds.value = []
|
||||
} catch (error) {
|
||||
lookupResult.value = null
|
||||
lookupErrorMessage.value = error instanceof Error ? error.message : '订单商品查询失败'
|
||||
} finally {
|
||||
lookupLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadConfigs)
|
||||
</script>
|
||||
|
||||
@@ -443,6 +506,119 @@ 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">
|
||||
<div>
|
||||
@@ -703,10 +879,21 @@ onMounted(loadConfigs)
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.lookup-grid,
|
||||
.lookup-summary {
|
||||
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;
|
||||
@@ -733,6 +920,31 @@ onMounted(loadConfigs)
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.lookup-actions {
|
||||
margin-top: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.lookup-summary {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.lookup-summary-item {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid rgba(148, 163, 184, 0.14);
|
||||
}
|
||||
|
||||
.lookup-summary-item strong {
|
||||
color: #1d3555;
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
@@ -932,6 +1144,7 @@ onMounted(loadConfigs)
|
||||
@media (max-width: 980px) {
|
||||
.panel-header,
|
||||
.section-title-row,
|
||||
.lookup-actions,
|
||||
.binding-actions,
|
||||
.binding-header {
|
||||
flex-direction: column;
|
||||
@@ -939,6 +1152,8 @@ onMounted(loadConfigs)
|
||||
}
|
||||
|
||||
.hint-grid,
|
||||
.lookup-grid,
|
||||
.lookup-summary,
|
||||
.binding-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user