增加 履约配置 手动查询
This commit is contained in:
@@ -1,16 +1,22 @@
|
||||
https://cdk.linux.do/receive/069af7bc-7915-48b6-834b-cf52eb00b798
|
||||
|
||||
这条“半自动+人工”链路已经落下第一版了。
|
||||
|
||||
后端现在新增了 support 角色,并把半自动档案 tencent_claim_assisted 接进了履约目录。用户侧领取页现在只允许“创建登录会话 + 扫码登录”,如果任务是半自动客服模式,用户自己再点“确认角色/开始兑换”会被后端直接拦住;客服则可以在任务详情里拿领取链接、看登录后的复核截图、确认角色、再触发后端自动兑换。关键入口在 admin-service.js、claim-session-service.js、session.js、fulfillment-bootstrap-service.js。
|
||||
cloudflared tunnel create mac-mini-tunnel
|
||||
|
||||
权限也一起收紧了。support 看不到库存凭据、原始 claim token、库存管理、Webhook、配置、审计、用户管理;任务详情接口本身已经做了脱敏,不只是前端隐藏。前端这边补了客服角色导航、路由限制和任务操作按钮,配置页也新增了“腾讯领取兑换(半自动+人工)”选项,相关界面在 AdminTaskDetailView.vue、AdminLayout.vue、AdminFulfillmentBindingsView.vue、AdminUsersView.vue。
|
||||
yml@ymlMacBook-Air ~ % cloudflared tunnel create mac-mini-tunnel
|
||||
Tunnel credentials written to /Users/yml/.cloudflared/562f9639-71f2-44e0-a5ef-b34bd4e215e5.json. cloudflared chose this file based on where your origin certificate was found. Keep this file secret. To revoke these credentials, delete the tunnel.
|
||||
|
||||
我做了后端文件的语法检查,node --check 通过;前端没法做完整 typecheck,因为当前环境里 vue-tsc 没装,npm run typecheck 会直接报 vue-tsc: command not found。
|
||||
你现在可以直接这样试:
|
||||
Created tunnel mac-mini-tunnel with id 562f9639-71f2-44e0-a5ef-b34bd4e215e5
|
||||
yml@ymlMacBook-Air ~ %
|
||||
|
||||
后台用户里新增一个 support 账号。
|
||||
在履约配置里把目标商品改成 tencent_claim_assisted。
|
||||
下一个测试单后,让用户打开领取链接并扫码登录。
|
||||
客服进入任务详情,确认能看到领取链接和复核截图,但看不到兑换码。
|
||||
客服点“客服确认角色”再点“客服开始兑换”。
|
||||
如果你愿意,我下一步可以继续把“任务列表页也做客服专用态优化”以及“客服复核截图/角色状态做成更明显的状态块”。
|
||||
|
||||
221329.cc.cd
|
||||
|
||||
4. # 例子:把 web.yourdomain.com 指向刚才创建的隧道
|
||||
cloudflared tunnel route dns mac-mini-tunnel 221329.cc.cd
|
||||
|
||||
|
||||
5. 启动隧道(临时/测试)
|
||||
如果你只是临时跑一个服务(比如你 OrbStack 里的容器映射到了本地 80 端口):
|
||||
|
||||
cloudflared tunnel run --url http://localhost:80 mac-mini-tunnel
|
||||
@@ -3,6 +3,7 @@ import { Router } from 'express'
|
||||
import {
|
||||
getAdminAgisoShopConfigs,
|
||||
getAdminFulfillmentBindingConfigs,
|
||||
lookupAdminFulfillmentBindingOrder,
|
||||
updateAdminAgisoShopConfigs,
|
||||
updateAdminFulfillmentBindingConfigs,
|
||||
} from '../../services/admin/admin-service.js'
|
||||
@@ -48,6 +49,30 @@ router.get('/platform-config/fulfillment-bindings', createJsonHandler(
|
||||
},
|
||||
))
|
||||
|
||||
router.post('/platform-config/fulfillment-bindings/lookup-order', createJsonHandler(
|
||||
(req) => lookupAdminFulfillmentBindingOrder(req.body),
|
||||
{
|
||||
successMessage: '订单商品查询成功',
|
||||
errorMessage: '手动查询订单商品失败',
|
||||
scope: '[admin/platform-config/fulfillment-bindings/lookup-order]',
|
||||
audit: (req, data) => ({
|
||||
action: 'platform_fulfillment_order_lookup',
|
||||
targetType: 'platform_config',
|
||||
targetId: [
|
||||
data.order?.provider || 'unknown',
|
||||
data.order?.platform || 'unknown',
|
||||
data.order?.shopId || 'unknown',
|
||||
data.order?.platformOrderId || 'unknown',
|
||||
].join(':'),
|
||||
data: {
|
||||
itemCount: Array.isArray(data.items) ? data.items.length : 0,
|
||||
shopId: data.order?.shopId || String(req.body?.shopId || '').trim(),
|
||||
platformOrderId: data.order?.platformOrderId || String(req.body?.platformOrderId || '').trim(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
))
|
||||
|
||||
router.post('/platform-config/fulfillment-bindings', createJsonHandler(
|
||||
(req) => updateAdminFulfillmentBindingConfigs(req.body),
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
getAgisoShopsFilePath,
|
||||
saveAgisoShopConfigMap,
|
||||
} from '../platforms/agiso/shop-config-service.js'
|
||||
import { enrichAgisoXianyuTradeOrder } from '../platforms/agiso/xianyu/order-detail-service.js'
|
||||
import {
|
||||
getOrderFulfillmentBindingConfigs,
|
||||
getOrderFulfillmentBindingsFilePath,
|
||||
@@ -688,8 +689,7 @@ export async function getAdminFulfillmentBindingConfigs() {
|
||||
return {
|
||||
filePath: getOrderFulfillmentBindingsFilePath(),
|
||||
bindings: bindings.map(mapAdminFulfillmentBindingConfigItem),
|
||||
observedProducts: rowsResult.rows.map((row) => {
|
||||
const item = {
|
||||
observedProducts: rowsResult.rows.map((row) => mapAdminObservedProductItem({
|
||||
provider: String(row.provider || '').trim(),
|
||||
platform: String(row.platform || '').trim(),
|
||||
shopId: String(row.shop_id || '').trim(),
|
||||
@@ -699,11 +699,128 @@ export async function getAdminFulfillmentBindingConfigs() {
|
||||
externalSkuName: String(row.external_sku_name || '').trim(),
|
||||
latestSeenAt: row.latest_seen_at || null,
|
||||
orderItemCount: Number(row.order_item_count || 0),
|
||||
}, bindings)),
|
||||
}
|
||||
}
|
||||
|
||||
export async function lookupAdminFulfillmentBindingOrder(payload = {}) {
|
||||
const provider = String(payload.provider || 'agiso').trim() || 'agiso'
|
||||
const platform = String(payload.platform || 'xianyu').trim() || 'xianyu'
|
||||
const shopId = String(payload.shopId || '').trim()
|
||||
const platformOrderId = String(payload.platformOrderId || '').trim()
|
||||
|
||||
if (!shopId) {
|
||||
throw createHttpError('请先填写店铺 ID', {
|
||||
statusCode: 400,
|
||||
errorCode: 'admin_fulfillment_lookup_missing_shop_id',
|
||||
})
|
||||
}
|
||||
|
||||
if (!platformOrderId) {
|
||||
throw createHttpError('请先填写平台订单号', {
|
||||
statusCode: 400,
|
||||
errorCode: 'admin_fulfillment_lookup_missing_platform_order_id',
|
||||
})
|
||||
}
|
||||
|
||||
if (provider !== 'agiso' || platform !== 'xianyu') {
|
||||
throw createHttpError('目前仅支持 Agiso 咸鱼订单手动查询', {
|
||||
statusCode: 400,
|
||||
errorCode: 'admin_fulfillment_lookup_platform_not_supported',
|
||||
})
|
||||
}
|
||||
|
||||
const detailResult = await enrichAgisoXianyuTradeOrder({
|
||||
provider,
|
||||
platform,
|
||||
shopId,
|
||||
shopName: '',
|
||||
platformOrderId,
|
||||
orderStatus: 'created',
|
||||
payStatus: 'unpaid',
|
||||
buyerId: '',
|
||||
buyerName: '',
|
||||
receiverContact: '',
|
||||
totalAmount: 0,
|
||||
currency: 'CNY',
|
||||
paidAt: null,
|
||||
rawPayload: {},
|
||||
items: [],
|
||||
}, {
|
||||
requestId: `admin-fulfillment-lookup:${shopId}:${platformOrderId}`,
|
||||
})
|
||||
|
||||
const detail = isPlainObject(detailResult?.parsed) ? detailResult.parsed : {}
|
||||
const items = Array.isArray(detail.items) ? detail.items : []
|
||||
|
||||
if (items.length === 0) {
|
||||
const detailReason = String(detailResult?.reason || '').trim()
|
||||
const detailMessage = String(detailResult?.errorMessage || '').trim()
|
||||
let message = detailMessage
|
||||
|
||||
if (!message && detailReason === 'missing_config') {
|
||||
message = '当前店铺缺少订单详情查询配置,请先检查 accessToken、appSecret 和详情接口地址'
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
message = `未查询到订单 ${platformOrderId} 的商品明细`
|
||||
}
|
||||
|
||||
throw createHttpError(message, {
|
||||
statusCode: 404,
|
||||
errorCode: 'admin_fulfillment_lookup_order_items_not_found',
|
||||
})
|
||||
}
|
||||
|
||||
const bindings = getOrderFulfillmentBindingConfigs()
|
||||
const resolvedShopName = pickFirstNonEmpty([
|
||||
detail.shopName,
|
||||
getAgisoShopConfig(shopId)?.shopName,
|
||||
])
|
||||
|
||||
return {
|
||||
order: {
|
||||
provider,
|
||||
platform,
|
||||
shopId,
|
||||
shopName: resolvedShopName,
|
||||
platformOrderId,
|
||||
buyerName: String(detail.buyerName || '').trim(),
|
||||
totalAmountFen: Number(detail.totalAmount || 0),
|
||||
totalAmount: formatFenToAmount(detail.totalAmount),
|
||||
paidAt: detail.paidAt || null,
|
||||
enriched: Boolean(detailResult?.enriched),
|
||||
enrichReason: String(detailResult?.reason || '').trim(),
|
||||
errorMessage: String(detailResult?.errorMessage || '').trim(),
|
||||
},
|
||||
items: items.map((item, index) => {
|
||||
const observed = {
|
||||
provider,
|
||||
platform,
|
||||
shopId,
|
||||
shopName: resolvedShopName,
|
||||
externalItemId: pickFirstNonEmpty([item?.externalItemId, item?.itemId]),
|
||||
externalSkuCode: pickFirstNonEmpty([item?.externalSkuCode, item?.skuCode, item?.externalItemId, item?.itemId]),
|
||||
externalSkuName: pickFirstNonEmpty([item?.externalSkuName, item?.skuName]),
|
||||
latestSeenAt: null,
|
||||
orderItemCount: Math.max(1, Number(item?.quantity || 0) || 1),
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
configured: bindings.some((binding) => matchesObservedProduct(binding, item)),
|
||||
lineId: [
|
||||
platformOrderId,
|
||||
index + 1,
|
||||
observed.externalSkuCode || 'na',
|
||||
observed.externalItemId || 'na',
|
||||
].join(':'),
|
||||
itemTitle: pickFirstNonEmpty([
|
||||
item?.skuName,
|
||||
item?.externalSkuName,
|
||||
item?.externalSkuCode,
|
||||
item?.externalItemId,
|
||||
]),
|
||||
quantity: observed.orderItemCount,
|
||||
...mapAdminObservedProductItem(observed, bindings),
|
||||
}
|
||||
}),
|
||||
}
|
||||
@@ -1253,6 +1370,34 @@ function matchesObservedProduct(binding, observed) {
|
||||
)
|
||||
}
|
||||
|
||||
function mapAdminObservedProductItem(item, bindings = []) {
|
||||
const matchedBinding = findMatchingObservedBinding(bindings, item)
|
||||
|
||||
return {
|
||||
provider: String(item?.provider || '').trim(),
|
||||
platform: String(item?.platform || '').trim(),
|
||||
shopId: String(item?.shopId || '').trim(),
|
||||
shopName: String(item?.shopName || '').trim(),
|
||||
externalItemId: String(item?.externalItemId || '').trim(),
|
||||
externalSkuCode: String(item?.externalSkuCode || '').trim(),
|
||||
externalSkuName: String(item?.externalSkuName || '').trim(),
|
||||
latestSeenAt: item?.latestSeenAt || null,
|
||||
orderItemCount: Number(item?.orderItemCount || 0),
|
||||
configured: Boolean(matchedBinding),
|
||||
matchedBinding: matchedBinding
|
||||
? {
|
||||
skuCode: String(matchedBinding.skuCode || '').trim(),
|
||||
skuName: String(matchedBinding.skuName || '').trim(),
|
||||
profileKey: String(matchedBinding.profileKey || '').trim(),
|
||||
}
|
||||
: null,
|
||||
}
|
||||
}
|
||||
|
||||
function findMatchingObservedBinding(bindings, observed) {
|
||||
return (Array.isArray(bindings) ? bindings : []).find((binding) => matchesObservedProduct(binding, observed)) || null
|
||||
}
|
||||
|
||||
async function mapAdminWebhookEvent(item, { includeRaw = false } = {}) {
|
||||
const headers = normalizeRecord(safeParseJson(item.headers_json))
|
||||
const query = normalizeRecord(safeParseJson(item.query_json))
|
||||
|
||||
@@ -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