统一寄卖仓库与提报审核规格展示

- 规格标签只显示值,0 璀璨不再显示「无」
- 仓库列表长规格文本改为纯值一行
- 列表物品名展示层清理历史 0 璀璨残留
- 补充规格展示回归测试
This commit is contained in:
2026-11-14 14:23:09 +08:00
parent 0dff00de97
commit a5e877b2ff
5 changed files with 264 additions and 66 deletions
@@ -67,8 +67,10 @@ import { buildConsignmentCategoryGroups } from '@/utils/consignment-category-tre
import { describeGiftEvidence, resolveGiftEvidenceBadge } from '@/utils/consignment-evidence'
import {
CONSIGNMENT_SPEC_TAG_COLORS,
resolveConsignmentSpecDisplayItems,
resolveConsignmentDisplayItemName,
resolveConsignmentSpecTagItems,
resolveConsignmentSpecText,
resolveConsignmentSpecValueText,
} from '@/utils/consignment-spec-display'
import { CONSIGNMENT_SYSTEM_OPTIONS, CONSIGNMENT_ZONE_OPTIONS } from '@/utils/consignment-spec'
import { formatDateTime } from '@/utils/date-time'
@@ -480,64 +482,69 @@ export default function ConsignmentInventoryPanel() {
title: '物品/寄卖单号',
key: 'item',
width: 240,
render: (_, record) => (
<div>
<div style={{ fontWeight: 600, fontSize: 14 }}>{record.itemName}</div>
{record.specText ? (
<Text type="secondary" style={{ fontSize: 12 }}>
{record.specText}
</Text>
) : null}
<Space size={4} align="center">
<Text type="secondary" style={{ fontSize: 12 }}>
寄卖单号
</Text>
<Text copyable style={{ fontSize: 12, fontWeight: 600 }}>
{resolveConsignmentNo(record)}
</Text>
</Space>
{record.giftWorkOrderNo ? (
render: (_, record) => {
const specLine = resolveConsignmentSpecValueText(record, { omitZeroBrilliance: true })
return (
<div>
<div style={{ fontWeight: 600, fontSize: 14 }}>
{resolveConsignmentDisplayItemName(record)}
</div>
{specLine ? (
<Text type="secondary" style={{ fontSize: 12 }}>
{specLine}
</Text>
) : null}
<Space size={4} align="center">
<Text type="secondary" style={{ fontSize: 12 }}>
寄卖单号
</Text>
<Text copyable style={{ fontSize: 12, fontWeight: 600 }}>
{resolveConsignmentNo(record)}
</Text>
</Space>
{record.giftWorkOrderNo ? (
<div style={{ marginTop: 2 }}>
<Space size={4} align="center" wrap>
<Text type="secondary" style={{ fontSize: 12 }}>
关联工单
</Text>
<Text copyable style={{ fontSize: 12, fontWeight: 600, color: '#1677ff' }}>
{record.giftWorkOrderNo}
</Text>
{record.giftWorkOrderStatus ? (
<Tag
color="cyan"
style={{
fontSize: 10,
lineHeight: '16px',
padding: '0 4px',
marginInlineEnd: 0,
}}
>
{formatGiftWorkOrderStatus(record.giftWorkOrderStatus)}
</Tag>
) : null}
</Space>
</div>
) : null}
<div style={{ marginTop: 2 }}>
<Space size={4} align="center" wrap>
<Text type="secondary" style={{ fontSize: 12 }}>
关联工单
</Text>
<Text copyable style={{ fontSize: 12, fontWeight: 600, color: '#1677ff' }}>
{record.giftWorkOrderNo}
</Text>
{record.giftWorkOrderStatus ? (
<Tag
color="cyan"
style={{
fontSize: 10,
lineHeight: '16px',
padding: '0 4px',
marginInlineEnd: 0,
}}
>
{formatGiftWorkOrderStatus(record.giftWorkOrderStatus)}
</Tag>
) : null}
</Space>
{record.sourceType === 'worker_portal' ? (
<Tag color="purple" style={{ fontSize: 11, padding: '0 4px' }}>
打手端提报
</Tag>
) : record.sourceType === 'recycle' ? (
<Tag color="green" style={{ fontSize: 11, padding: '0 4px' }}>
回收
</Tag>
) : (
<Tag color="blue" style={{ fontSize: 11, padding: '0 4px' }}>
人工入库
</Tag>
)}
</div>
) : null}
<div style={{ marginTop: 2 }}>
{record.sourceType === 'worker_portal' ? (
<Tag color="purple" style={{ fontSize: 11, padding: '0 4px' }}>
打手端提报
</Tag>
) : record.sourceType === 'recycle' ? (
<Tag color="green" style={{ fontSize: 11, padding: '0 4px' }}>
回收
</Tag>
) : (
<Tag color="blue" style={{ fontSize: 11, padding: '0 4px' }}>
人工入库
</Tag>
)}
</div>
</div>
),
)
},
},
{
title: '寄卖分类',
@@ -562,7 +569,7 @@ export default function ConsignmentInventoryPanel() {
width: 220,
render: (_, record) => {
// 规格按该分类已发布的寄卖模板渲染;没有规格配置的历史库存才回退旧字段。
const specItems = resolveConsignmentSpecDisplayItems(record.specs, record.specValues)
const specItems = resolveConsignmentSpecTagItems(record.specs, record.specValues)
if (specItems.length > 0) {
return (
<Space size={4} wrap>
@@ -570,8 +577,10 @@ export default function ConsignmentInventoryPanel() {
<Tag
key={item.key}
color={CONSIGNMENT_SPEC_TAG_COLORS[index % CONSIGNMENT_SPEC_TAG_COLORS.length]}
title={`${item.label}:${item.value}`}
style={{ marginInlineEnd: 0, fontSize: 11, padding: '0 6px' }}
>
{item.label} {item.value}
{item.value}
</Tag>
))}
</Space>
@@ -46,7 +46,8 @@ import { formatConsignmentContactSupporter } from '@/utils/consignment-contact'
import { describeGiftEvidence, resolveGiftEvidenceBadge } from '@/utils/consignment-evidence'
import {
CONSIGNMENT_SPEC_TAG_COLORS,
resolveConsignmentSpecDisplayItems,
resolveConsignmentDisplayItemName,
resolveConsignmentSpecTagItems,
resolveConsignmentSpecText,
} from '@/utils/consignment-spec-display'
import { CONSIGNMENT_SYSTEM_OPTIONS, CONSIGNMENT_ZONE_OPTIONS } from '@/utils/consignment-spec'
@@ -245,18 +246,20 @@ export default function ConsignmentSubmissionsPanel() {
width: 230,
render: (_, record) => {
// 规格按该分类已发布的寄卖模板渲染;没配置规格的历史提报才回退旧字段。
const specItems = resolveConsignmentSpecDisplayItems(record.specs, record.specValues)
const specItems = resolveConsignmentSpecTagItems(record.specs, record.specValues)
return (
<Space direction="vertical" size={2}>
<div style={{ fontWeight: 600 }}>{record.itemName}</div>
<div style={{ fontWeight: 600 }}>{resolveConsignmentDisplayItemName(record)}</div>
<Space size={4} wrap>
{specItems.length > 0 ? (
specItems.map((item, index) => (
<Tag
key={item.key}
color={CONSIGNMENT_SPEC_TAG_COLORS[index % CONSIGNMENT_SPEC_TAG_COLORS.length]}
title={`${item.label}:${item.value}`}
style={{ marginInlineEnd: 0, fontSize: 11, padding: '0 6px' }}
>
{item.label} {item.value}
{item.value}
</Tag>
))
) : (
@@ -72,6 +72,56 @@ export function formatLegacyConsignmentSpec(record: ConsignmentLegacySpecRecord)
return parts.join(' / ')
}
/**
* 列表用规格标签:只保留值,0 璀璨属于默认值,不占标签位。
* 详情/审核弹窗需要字段名时仍用 resolveConsignmentSpecDisplayItems。
*/
export function resolveConsignmentSpecTagItems(
specs: ConsignmentSpecField[] | null | undefined,
values: ConsignmentSpecValues | null | undefined,
): ConsignmentSpecDisplayItem[] {
return resolveConsignmentSpecDisplayItems(specs, values).filter((item) => {
if (item.key !== 'brillianceCount') return true
if (item.value === '无') return false
const raw = String(values?.[item.key] ?? '').trim()
const numeric = Number(raw)
return !(raw && Number.isFinite(numeric) && numeric <= 0)
})
}
/**
* 列表用纯值规格文本:不带字段名,方便和物品名并排扫读;0 璀璨省略。
* 没有动态规格的历史数据回退旧 5 项(本身也是纯值)。
*/
export function resolveConsignmentSpecValueText(
input: {
specs?: ConsignmentSpecField[] | null
specValues?: ConsignmentSpecValues | null
} & ConsignmentLegacySpecRecord,
options: { omitZeroBrilliance?: boolean } = {},
): string {
const items = options.omitZeroBrilliance
? resolveConsignmentSpecTagItems(input.specs, input.specValues)
: resolveConsignmentSpecDisplayItems(input.specs, input.specValues)
if (items.length > 0) return items.map((item) => item.value).join(' / ')
return formatLegacyConsignmentSpec(input)
}
/**
* 列表展示用物品名:历史 0 璀璨数据会留下「N钻璀璨」这种断尾单位,
* 只在展示层去掉,不改库;详情仍显示原始 item_name。
* 只匹配「钻」后面紧跟的「璀璨」,避免误伤系列名里真实包含的「璀璨」。
*/
export function resolveConsignmentDisplayItemName(input: {
itemName?: string | null
brillianceCount?: number | null
}): string {
const name = String(input.itemName || '').trim()
if (!name) return ''
if (Number(input.brillianceCount || 0) > 0) return name
return name.replace(/(\d+\s*钻)\s*(?:0\s*)?璀璨/g, '$1').trim()
}
/**
* 详情页规格文本:后端 specText 优先(已含选项名称),其次按模板现拼,最后回退旧字段。
*/
@@ -14,8 +14,17 @@ test('提报审核:规格按寄卖模板渲染,有模板时不再写死旧
const page = readPanel('ConsignmentSubmissionsPanel.tsx')
assert.ok(
page.includes('resolveConsignmentSpecDisplayItems(record.specs, record.specValues)'),
'提报列表必须按模板字段渲染规格',
page.includes('resolveConsignmentSpecTagItems(record.specs, record.specValues)'),
'提报列表必须按模板字段渲染规格标签',
)
assert.ok(
page.includes('resolveConsignmentDisplayItemName(record)'),
'提报列表物品名必须清掉历史 0 璀璨残留',
)
assert.ok(!page.includes('{item.label} {item.value}'), '提报列表规格标签只显示值')
assert.ok(
page.includes('title={`${item.label}:${item.value}`}'),
'提报列表规格标签悬浮显示字段名',
)
assert.ok(
page.includes('resolveConsignmentSpecText(viewingSubmission)'),
@@ -40,8 +49,21 @@ test('寄售仓库:列表/详情按模板渲染,编辑不再写死旧字段'
assert.ok(!page.includes("title: '和平精英规格'"), '仓库列表列名不得写死和平精英')
assert.ok(page.includes("title: '寄卖规格'"), '仓库列表列名应为通用「寄卖规格」')
assert.ok(
page.includes('resolveConsignmentSpecDisplayItems(record.specs, record.specValues)'),
'仓库列表必须按模板字段渲染规格',
page.includes('resolveConsignmentSpecTagItems(record.specs, record.specValues)'),
'仓库列表必须按模板字段渲染规格标签',
)
assert.ok(
page.includes('resolveConsignmentSpecValueText(record, { omitZeroBrilliance: true })'),
'仓库列表规格行必须只显示值并省略 0 璀璨',
)
assert.ok(
page.includes('resolveConsignmentDisplayItemName(record)'),
'仓库列表物品名必须清掉历史 0 璀璨残留',
)
assert.ok(!page.includes('{record.specText}'), '仓库列表不得再显示重复的长规格文本')
assert.ok(
page.includes('title={`${item.label}:${item.value}`}'),
'仓库列表规格标签悬浮显示字段名',
)
assert.ok(
page.includes('resolveConsignmentSpecText(viewingItem)'),
@@ -4,8 +4,11 @@ import test from 'node:test'
import type { ConsignmentSpecField } from '../src/types/consignment.ts'
import {
formatLegacyConsignmentSpec,
resolveConsignmentDisplayItemName,
resolveConsignmentSpecDisplayItems,
resolveConsignmentSpecTagItems,
resolveConsignmentSpecText,
resolveConsignmentSpecValueText,
} from '../src/utils/consignment-spec-display.ts'
function makeSpec(
@@ -110,3 +113,114 @@ test('规格文本:没有模板规格时回退旧列,0 璀璨不展示', ()
assert.equal(formatLegacyConsignmentSpec({ diamondLevel: 1, brillianceCount: 0 }), '1钻')
assert.equal(resolveConsignmentSpecText({}), '-')
})
const diamondSpec = makeSpec({
key: 'diamondLevel',
label: '钻石等级',
type: 'select',
sortOrder: 2,
options: [
{ label: '3钻', value: '3', enabled: true },
{ label: '4钻', value: '4', enabled: true },
],
})
const brillianceSpec = makeSpec({
key: 'brillianceCount',
label: '璀璨数',
type: 'select',
sortOrder: 3,
options: [
{ label: '无', value: '0', enabled: true },
{ label: '1璀璨', value: '1', enabled: true },
{ label: '2璀璨', value: '2', enabled: true },
],
})
test('列表规格标签:只保留值,0 璀璨不占标签位', () => {
const zero = resolveConsignmentSpecTagItems([systemSpec, diamondSpec, brillianceSpec], {
system: 'ios',
diamondLevel: '4',
brillianceCount: '0',
})
assert.deepEqual(
zero.map((item) => item.value),
['苹果', '4钻'],
)
const withBrilliance = resolveConsignmentSpecTagItems([systemSpec, diamondSpec, brillianceSpec], {
system: 'ios',
diamondLevel: '4',
brillianceCount: '2',
})
assert.deepEqual(
withBrilliance.map((item) => item.value),
['苹果', '4钻', '2璀璨'],
)
})
test('列表规格纯值文本:不含字段名,0 璀璨省略', () => {
assert.equal(
resolveConsignmentSpecValueText(
{
specs: [systemSpec, diamondSpec, brillianceSpec],
specValues: { system: 'ios', diamondLevel: '4', brillianceCount: '0' },
},
{ omitZeroBrilliance: true },
),
'苹果 / 4钻',
)
assert.equal(
resolveConsignmentSpecValueText(
{
specs: [systemSpec, diamondSpec, brillianceSpec],
specValues: { system: 'ios', diamondLevel: '4', brillianceCount: '1' },
},
{ omitZeroBrilliance: true },
),
'苹果 / 4钻 / 1璀璨',
)
// 没有动态规格的历史提报/库存回退旧字段,本身就是纯值
assert.equal(
resolveConsignmentSpecValueText({
system: '苹果',
serverZone: '微信区',
series: '暗潮',
diamondLevel: 3,
brillianceCount: 2,
}),
'苹果 / 微信区 / 暗潮 / 3钻 / 2璀璨',
)
})
test('列表物品名:0 璀璨的历史断尾单位只在展示层清掉', () => {
assert.equal(
resolveConsignmentDisplayItemName({
itemName: '安卓暗潮2钻璀璨 苏尔南',
brillianceCount: 0,
}),
'安卓暗潮2钻 苏尔南',
)
assert.equal(
resolveConsignmentDisplayItemName({
itemName: '安卓暗潮2钻0璀璨 苏尔南',
brillianceCount: 0,
}),
'安卓暗潮2钻 苏尔南',
)
assert.equal(
resolveConsignmentDisplayItemName({
itemName: '苹果血宴1钻1璀璨 苏尔南',
brillianceCount: 1,
}),
'苹果血宴1钻1璀璨 苏尔南',
)
// 系列名本身含「璀璨」时不能误删
assert.equal(
resolveConsignmentDisplayItemName({
itemName: '璀璨系列2钻 苏尔南',
brillianceCount: 0,
}),
'璀璨系列2钻 苏尔南',
)
})