feat(admin): 订单列表只展示平台判断 SKU
This commit is contained in:
@@ -49,6 +49,7 @@ test('mapAdminOrderListItem formats itemTitle, skuNick, and summary correctly fo
|
||||
|
||||
assert.equal(mapped.itemTitle, '和平精英地铁逃生装备满钻低价苏尔南怒嚎暗潮手枪璀璨无暇1到7钻')
|
||||
assert.equal(mapped.skuNick, '安卓血宴2钻')
|
||||
assert.equal(mapped.matchSku, '安卓血宴2钻')
|
||||
assert.equal(
|
||||
mapped.itemSummary,
|
||||
'和平精英地铁逃生装备满钻低价苏尔南怒嚎暗潮手枪璀璨无暇1到7钻 (安卓血宴2钻)',
|
||||
@@ -59,6 +60,7 @@ test('mapAdminOrderListItem formats itemTitle, skuNick, and summary correctly fo
|
||||
{
|
||||
itemTitle: '和平精英地铁逃生装备满钻低价苏尔南怒嚎暗潮手枪璀璨无暇1到7钻',
|
||||
skuNick: '安卓血宴2钻',
|
||||
matchSku: '安卓血宴2钻',
|
||||
quantity: 1,
|
||||
},
|
||||
])
|
||||
@@ -133,6 +135,7 @@ test('mapAdminOrderListItem falls back to PDD goods_name/out_sku_sn when orderIt
|
||||
|
||||
assert.equal(mapped.itemTitle, '和平精英地铁逃生苏尔南暗潮怒嚎血宴')
|
||||
assert.equal(mapped.skuNick, '安卓怒嚎3钻')
|
||||
assert.equal(mapped.matchSku, '安卓怒嚎3钻')
|
||||
assert.equal(mapped.itemCount, 1)
|
||||
assert.equal(mapped.totalQuantity, 1)
|
||||
})
|
||||
@@ -211,4 +214,48 @@ test('抖音码速达:商品列展示内部 SKU,引流标题降级为 skuNic
|
||||
assert.equal(mapped.skuNick, '【测试专用】和平精英皮肤小店入口出售返场吃鸡皮肤浪漫天命专用')
|
||||
// 列表摘要同样以内部 SKU 为主
|
||||
assert.match(summarizeOrderItems(orderItems, order), /^幸运币2个/)
|
||||
// 列表只展示判断用的内部 SKU,不能退回引流标题
|
||||
assert.equal(mapped.matchSku, '幸运币2个')
|
||||
assert.equal(mapped.items?.[0]?.matchSku, '幸运币2个')
|
||||
})
|
||||
|
||||
test('拼多多匹配 SKU 优先 sku_name,不用带地区的长 spec', () => {
|
||||
const order: OrderListRow = {
|
||||
id: 100,
|
||||
provider: 'pdd',
|
||||
platform: 'pdd',
|
||||
shop_id: 'pdd-shop',
|
||||
shop_name: '拼多多店铺',
|
||||
platform_order_id: '260915-72034516082531',
|
||||
order_status: 'paid',
|
||||
pay_status: 'paid',
|
||||
buyer_id: '',
|
||||
buyer_name: '',
|
||||
receiver_contact: '',
|
||||
total_amount: 100,
|
||||
currency: 'CNY',
|
||||
raw_payload_json: {},
|
||||
paid_at: '2026-10-09T04:55:05.000Z',
|
||||
created_at: '2026-10-09T04:55:05.000Z',
|
||||
updated_at: '2026-10-09T04:55:05.000Z',
|
||||
}
|
||||
|
||||
const orderItems: OrderItemRow[] = [
|
||||
{
|
||||
id: 1,
|
||||
order_id: 100,
|
||||
sku_code: '安卓怒嚎1钻',
|
||||
sku_name: '安卓怒嚎1钻',
|
||||
quantity: 1,
|
||||
spec_json: JSON.stringify({
|
||||
itemTitle: '和平精英地铁逃生苏尔南暗潮怒嚎血宴',
|
||||
skuNick: '安卓区(qv互通),苏尔南-怒嚎1钻',
|
||||
}),
|
||||
item_snapshot_json: JSON.stringify({}),
|
||||
},
|
||||
]
|
||||
|
||||
const mapped = mapAdminOrderListItem(order, { orderItems })
|
||||
assert.equal(mapped.matchSku, '安卓怒嚎1钻')
|
||||
assert.equal(mapped.items?.[0]?.matchSku, '安卓怒嚎1钻')
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { formatFenToAmount, normalizeFen } from '../../utils/money.js'
|
||||
import {
|
||||
resolveDisplayShopName,
|
||||
resolveOrderItemMatchSku,
|
||||
resolveOrderItemSkuNick,
|
||||
resolveOrderItemTitle,
|
||||
} from './admin-read-shared-helpers.js'
|
||||
@@ -34,28 +35,34 @@ export function mapAdminOrderListItem(
|
||||
|
||||
let resolvedItemTitle = ''
|
||||
let resolvedSkuNick = ''
|
||||
let resolvedMatchSku = ''
|
||||
let itemsList: AdminOrderItemSummary[] = []
|
||||
|
||||
if (normalizedItems.length > 0) {
|
||||
itemsList = normalizedItems.map((orderItem) => {
|
||||
const itTitle = resolveOrderItemTitle(orderItem, item, vouchers)
|
||||
const itSkuNick = resolveOrderItemSkuNick(orderItem, item, vouchers)
|
||||
const itMatchSku = resolveOrderItemMatchSku(orderItem, item, vouchers)
|
||||
return {
|
||||
itemTitle: itTitle,
|
||||
skuNick: itSkuNick,
|
||||
matchSku: itMatchSku,
|
||||
quantity: Math.max(1, Number(orderItem.quantity || 1)),
|
||||
}
|
||||
})
|
||||
resolvedItemTitle = itemsList[0]?.itemTitle || ''
|
||||
resolvedSkuNick = itemsList[0]?.skuNick || ''
|
||||
resolvedMatchSku = itemsList[0]?.matchSku || ''
|
||||
} else {
|
||||
resolvedItemTitle = resolveOrderItemTitle(null, item, vouchers)
|
||||
resolvedSkuNick = resolveOrderItemSkuNick(null, item, vouchers)
|
||||
if (resolvedItemTitle || resolvedSkuNick) {
|
||||
resolvedMatchSku = resolveOrderItemMatchSku(null, item, vouchers)
|
||||
if (resolvedItemTitle || resolvedSkuNick || resolvedMatchSku) {
|
||||
itemsList = [
|
||||
{
|
||||
itemTitle: resolvedItemTitle,
|
||||
skuNick: resolvedSkuNick,
|
||||
matchSku: resolvedMatchSku,
|
||||
quantity: 1,
|
||||
},
|
||||
]
|
||||
@@ -99,6 +106,7 @@ export function mapAdminOrderListItem(
|
||||
itemSummary,
|
||||
itemTitle: resolvedItemTitle,
|
||||
skuNick: resolvedSkuNick,
|
||||
matchSku: resolvedMatchSku,
|
||||
items: itemsList,
|
||||
taskCount: tasks.length,
|
||||
workOrderCount,
|
||||
|
||||
@@ -637,6 +637,41 @@ export function resolveOrderItemSkuNick(
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表展示用的「平台判断 SKU」:只保留能用于匹配的短 SKU,不展示引流长标题。
|
||||
* 抖音的内部 SKU 存在 itemTitle(resolveOrderItemTitle 已优先返回 code),
|
||||
* 拼多多用 sku_name/sku_code 更干净,91/快手优先用 skuNick。
|
||||
*/
|
||||
export function resolveOrderItemMatchSku(
|
||||
item: Partial<OrderItemRow> | null | undefined,
|
||||
fallbackOrder?: Partial<OrderRow> | null | undefined,
|
||||
vouchers?: KuaishouIndustryVoucherRow[] | null | undefined,
|
||||
): string {
|
||||
const provider = String(fallbackOrder?.provider || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
const spec = item ? safeParseJson(item.spec_json) : {}
|
||||
const snapshot = item ? safeParseJson(item.item_snapshot_json) : {}
|
||||
const source = pickFirstNonEmpty([spec.source, snapshot.source]).toLowerCase()
|
||||
const isDouyin = provider.includes('douyin') || source.includes('douyin')
|
||||
|
||||
const itemTitle = resolveOrderItemTitle(item, fallbackOrder, vouchers)
|
||||
const skuNick = resolveOrderItemSkuNick(item, fallbackOrder, vouchers)
|
||||
const skuName = String(item?.sku_name || '').trim()
|
||||
const skuCode = String(item?.sku_code || '').trim()
|
||||
|
||||
if (isDouyin) {
|
||||
// 抖音 skuNick 往往是引流标题,内部 SKU 才是判断依据。
|
||||
return itemTitle || skuName || skuCode
|
||||
}
|
||||
|
||||
if (provider === 'pdd') {
|
||||
return skuName || skuCode || skuNick || itemTitle
|
||||
}
|
||||
|
||||
return skuNick || skuName || skuCode || itemTitle
|
||||
}
|
||||
|
||||
export function resolveOrderItemDeliveryMode(
|
||||
tasks: TaskLike[] | null | undefined,
|
||||
orderItemId: number | string,
|
||||
|
||||
@@ -9,6 +9,7 @@ export type AdminOrderFulfillmentProgress = {
|
||||
export type AdminOrderItemSummary = {
|
||||
itemTitle: string
|
||||
skuNick: string
|
||||
matchSku: string
|
||||
quantity: number
|
||||
}
|
||||
|
||||
@@ -35,6 +36,7 @@ export type AdminOrderListItem = {
|
||||
itemSummary: string
|
||||
itemTitle?: string
|
||||
skuNick?: string
|
||||
matchSku?: string
|
||||
items?: AdminOrderItemSummary[]
|
||||
taskCount: number
|
||||
resourceStatus: string
|
||||
|
||||
@@ -204,76 +204,55 @@ export default function AdminOrdersPage() {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '商品 / 规格',
|
||||
minWidth: 260,
|
||||
title: '平台 SKU',
|
||||
minWidth: 190,
|
||||
render: (_, row) => {
|
||||
const itemTitle = row.itemTitle || row.itemSummary || '-'
|
||||
const skuNick = row.skuNick
|
||||
const items = row.items && row.items.length > 0 ? row.items : null
|
||||
const fallbackSku = row.matchSku || row.skuNick || '未识别 SKU'
|
||||
|
||||
const renderSkuTag = (sku: string | undefined, title?: string, key?: string) => {
|
||||
const label = sku || '未识别 SKU'
|
||||
return (
|
||||
<Tooltip
|
||||
key={key}
|
||||
title={
|
||||
title && title !== label
|
||||
? `平台 SKU:${label}|原商品标题:${title}`
|
||||
: `平台 SKU:${label}`
|
||||
}
|
||||
>
|
||||
<Tag
|
||||
color={label === '未识别 SKU' ? 'default' : 'blue'}
|
||||
style={{
|
||||
margin: 0,
|
||||
maxWidth: 200,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="cell-stack" style={{ gap: 4 }}>
|
||||
{items && items.length > 1 ? (
|
||||
<div className="cell-stack" style={{ gap: 4 }}>
|
||||
{items.slice(0, 2).map((item, idx) => (
|
||||
<div key={idx} className="cell-stack" style={{ gap: 2 }}>
|
||||
<Typography.Text
|
||||
strong
|
||||
ellipsis={{ tooltip: item.itemTitle }}
|
||||
style={{ maxWidth: 360, fontSize: 13 }}
|
||||
>
|
||||
{item.itemTitle || '-'}
|
||||
</Typography.Text>
|
||||
{item.skuNick ? (
|
||||
<Space size={4} wrap>
|
||||
<Tag
|
||||
color="blue"
|
||||
style={{
|
||||
margin: 0,
|
||||
maxWidth: 320,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
fontSize: 12,
|
||||
}}
|
||||
title={`规格:${item.skuNick}`}
|
||||
>
|
||||
{item.skuNick}
|
||||
</Tag>
|
||||
</Space>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
{items.length > 2 ? <span className="muted">等 {items.length} 种商品</span> : null}
|
||||
{items
|
||||
.slice(0, 2)
|
||||
.map((item, idx) =>
|
||||
renderSkuTag(item.matchSku || item.skuNick, item.itemTitle, `sku-${idx}`),
|
||||
)}
|
||||
{items.length > 2 ? <span className="muted">等 {items.length} 项</span> : null}
|
||||
</div>
|
||||
) : items && items.length === 1 ? (
|
||||
renderSkuTag(items[0].matchSku || items[0].skuNick, items[0].itemTitle, 'sku-0')
|
||||
) : (
|
||||
<>
|
||||
<Typography.Text
|
||||
strong
|
||||
ellipsis={{ tooltip: itemTitle }}
|
||||
style={{ maxWidth: 360, fontSize: 13 }}
|
||||
>
|
||||
{itemTitle}
|
||||
</Typography.Text>
|
||||
{skuNick ? (
|
||||
<Space size={4} wrap>
|
||||
<Tag
|
||||
color="blue"
|
||||
style={{
|
||||
margin: 0,
|
||||
maxWidth: 320,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
fontSize: 12,
|
||||
}}
|
||||
title={`规格:${skuNick}`}
|
||||
>
|
||||
{skuNick}
|
||||
</Tag>
|
||||
</Space>
|
||||
) : null}
|
||||
</>
|
||||
renderSkuTag(fallbackSku, row.itemTitle || row.itemSummary, 'sku-0')
|
||||
)}
|
||||
<span className="muted" style={{ fontSize: 12 }}>
|
||||
数量 {row.totalQuantity || 1}
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface AdminOrderFulfillmentProgress {
|
||||
export interface AdminOrderItemSummary {
|
||||
itemTitle: string
|
||||
skuNick: string
|
||||
matchSku: string
|
||||
quantity: number
|
||||
}
|
||||
|
||||
@@ -37,6 +38,7 @@ export interface AdminOrderListItem {
|
||||
itemSummary: string
|
||||
itemTitle?: string
|
||||
skuNick?: string
|
||||
matchSku?: string
|
||||
items?: AdminOrderItemSummary[]
|
||||
taskCount: number
|
||||
resourceStatus: string
|
||||
|
||||
Reference in New Issue
Block a user