Update seller store experience

This commit is contained in:
R524809
2026-09-08 16:19:38 +08:00
parent ceb644a3f9
commit 319887726e
25 changed files with 31417 additions and 127 deletions
@@ -49,6 +49,7 @@ export default function CollectionInspectionPage() {
() => new Map((record?.variants ?? []).map((variant) => [variant.id, variant])),
[record]
);
const payload = normalisedPayload(record?.normalized_payload);
if (error)
return <Alert type="error" message="无法读取采集数据" description={error} showIcon />;
@@ -65,7 +66,7 @@ export default function CollectionInspectionPage() {
type="info"
showIcon
style={{ marginBottom: 16 }}
message="这里展示扩展实际提交、服务端实际保存的数据,不代表已确认的商品编辑字段。"
message="这里展示扩展实际提交、服务端实际保存的数据;未转存图片或视频直接使用采集来源预览,不代表已确认的商品编辑字段。"
/>
<Descriptions
bordered
@@ -81,9 +82,71 @@ export default function CollectionInspectionPage() {
{ key: 'status', label: '采集状态', children: record.collection_status },
{ key: 'parser', label: '解析器版本', children: record.parser_version ?? '—' },
{ key: 'sku', label: '已创建 SKU', children: record.variants.length },
{ key: 'assets', label: '素材任务', children: record.asset_transfers.length },
{ key: 'assets', label: '素材候选', children: record.asset_transfers.length },
{ key: 'attributes', label: '已采集特征', children: payload.attributes.length },
{ key: 'categories', label: '已采集类目', children: payload.categories.length },
]}
/>
<Typography.Title level={5} style={{ marginTop: 24 }}>
商品描述
</Typography.Title>
<Card size="small">
{payload.description ? (
<Typography.Paragraph style={{ whiteSpace: 'pre-wrap', marginBottom: 0 }}>
{payload.description}
</Typography.Paragraph>
) : (
<Typography.Text type="secondary">本次采集未取得商品描述。</Typography.Text>
)}
</Card>
<Typography.Title level={5} style={{ marginTop: 24 }}>
类目与特征
</Typography.Title>
<Descriptions
bordered
size="small"
column={1}
items={[
{
key: 'category-path',
label: '类目路径',
children: payload.categories.length
? payload.categories.map((category) => category.name).join(' › ')
: '未采集',
},
]}
/>
<Table
size="small"
pagination={false}
rowKey={(attribute) => `${attribute.key}:${attribute.value}`}
dataSource={payload.attributes}
style={{ marginTop: 12 }}
columns={[
{ title: '特征', dataIndex: 'name' },
{ title: '值', dataIndex: 'value' },
{ title: '来源键', dataIndex: 'key', ellipsis: true },
]}
locale={{ emptyText: '本次采集未取得商品特征。' }}
/>
<Typography.Title level={5} style={{ marginTop: 24 }}>
规格轴
</Typography.Title>
<Table
size="small"
pagination={false}
rowKey="key"
dataSource={payload.variantAxes}
columns={[
{ title: '规格名称', dataIndex: 'name' },
{ title: '来源键', dataIndex: 'key', ellipsis: true },
{
title: '规格值',
render: (_, axis) => axis.values.map((value) => value.name).join(' / ') || '—',
},
]}
locale={{ emptyText: '本次采集未取得规格轴。' }}
/>
<Typography.Title level={5} style={{ marginTop: 24 }}>
SKU 草稿
</Typography.Title>
@@ -111,7 +174,7 @@ export default function CollectionInspectionPage() {
]}
/>
<Typography.Title level={5} style={{ marginTop: 24 }}>
素材转存任务
素材候选与转存状态
</Typography.Title>
<Table
size="small"
@@ -124,26 +187,50 @@ export default function CollectionInspectionPage() {
width: 108,
render: (_, transfer) => {
const url = transfer.asset_id
? previewUrls[transfer.asset_id]
: undefined;
? previewUrls[transfer.asset_id] ?? transfer.source_url
: transfer.source_url;
if (transfer.media_type === 'video')
return url ? (
<video
controls
preload="none"
poster={transfer.poster_url ?? undefined}
src={url}
style={{ width: 96, height: 72, objectFit: 'cover', background: '#000' }}
>
当前浏览器不支持视频预览。
</video>
) : (
<Typography.Text type="secondary">
暂无来源视频
</Typography.Text>
);
if (url)
return (
<Image
width={72}
height={72}
src={url}
alt="已转存采集素材"
alt={
transfer.asset_id
? '已转存采集素材'
: '采集来源图片'
}
style={{ objectFit: 'cover' }}
/>
);
return (
<Typography.Text type="secondary">
{transfer.status === 'succeeded' ? '预览加载中…' : '待转存'}
暂无来源图片
</Typography.Text>
);
},
},
{ title: '类型', dataIndex: 'role' },
{
title: '类型',
render: (_, transfer) =>
`${transfer.media_type === 'video' ? '视频' : '图片'} · ${transfer.role}`,
},
{
title: '关联 SKU',
render: (_, transfer) => {
@@ -153,7 +240,20 @@ export default function CollectionInspectionPage() {
return variant?.source_sku ?? variant?.source_variant_key ?? '商品级';
},
},
{ title: '状态', dataIndex: 'status' },
{
title: '状态',
dataIndex: 'status',
render: (status) => {
if (status === 'pending_selection')
return <Tag color="default">候选,未转存</Tag>;
if (status === 'queued') return <Tag color="processing">等待转存</Tag>;
if (status === 'running') return <Tag color="processing">转存中</Tag>;
if (status === 'succeeded') return <Tag color="success">已转存</Tag>;
if (status === 'failed') return <Tag color="error">转存失败</Tag>;
if (status === 'cancelled') return <Tag>已取消</Tag>;
return status;
},
},
{
title: '来源 URL',
dataIndex: 'source_url',
@@ -229,3 +329,52 @@ function Json({ value }: { value: unknown }) {
</pre>
);
}
type InspectionAttribute = { key: string; name: string; value: string };
type InspectionCategory = { key: string; name: string };
type InspectionAxis = { key: string; name: string; values: Array<{ key: string; name: string }> };
function normalisedPayload(value: unknown): {
description: string | null;
attributes: InspectionAttribute[];
categories: InspectionCategory[];
variantAxes: InspectionAxis[];
} {
if (!value || typeof value !== 'object' || Array.isArray(value))
return { description: null, attributes: [], categories: [], variantAxes: [] };
const root = value as Record<string, unknown>;
const common = record(root.common);
const description = record(common?.description)?.value;
return {
description: typeof description === 'string' ? description : null,
attributes: records(common?.attributes).flatMap((item) =>
typeof item.key === 'string' && typeof item.name === 'string' && typeof item.value === 'string'
? [{ key: item.key, name: item.name, value: item.value }]
: []),
categories: records(common?.categories).flatMap((item) =>
typeof item.key === 'string' && typeof item.name === 'string'
? [{ key: item.key, name: item.name }]
: []),
variantAxes: records(root.variantAxes).flatMap((item) => {
if (typeof item.key !== 'string' || typeof item.name !== 'string') return [];
return [{
key: item.key,
name: item.name,
values: records(item.values).flatMap((option) =>
typeof option.key === 'string' && typeof option.name === 'string'
? [{ key: option.key, name: option.name }]
: []),
}];
}),
};
}
function record(value: unknown): Record<string, unknown> | undefined {
return value && typeof value === 'object' && !Array.isArray(value)
? value as Record<string, unknown>
: undefined;
}
function records(value: unknown): Record<string, unknown>[] {
return Array.isArray(value) ? value.filter((item): item is Record<string, unknown> => !!record(item)) : [];
}