增加 履约配置 手动查询

This commit is contained in:
yml2213
2026-04-13 16:46:33 +08:00
parent 050671e71b
commit 96d1644b1c
6 changed files with 461 additions and 29 deletions
+10
View File
@@ -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
+31
View File
@@ -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;
}