156 lines
3.7 KiB
TypeScript
156 lines
3.7 KiB
TypeScript
/**
|
||
* 从 SSR JSON 构建 ScanResult
|
||
*/
|
||
import { toOriginalUrl } from './url';
|
||
import type { SiteProfile } from '../profiles/types';
|
||
import type { SSRData } from './ssr';
|
||
|
||
// 直接定义类型避免循环依赖
|
||
interface TextMaterial {
|
||
kind: 'title' | 'price' | 'params' | 'desc';
|
||
content: string;
|
||
pairs?: Array<{ key: string; value: string }>;
|
||
}
|
||
|
||
interface ImageMaterial {
|
||
key: string;
|
||
groupKey: string;
|
||
groupName: string;
|
||
variantName?: string;
|
||
url: string;
|
||
thumbUrl: string;
|
||
index: number;
|
||
type: 'img' | 'video';
|
||
width?: number;
|
||
height?: number;
|
||
}
|
||
|
||
interface ScanResult {
|
||
platform: string;
|
||
itemId: string | null;
|
||
url: string;
|
||
texts: TextMaterial[];
|
||
images: ImageMaterial[];
|
||
scannedAt: number;
|
||
stats: Record<string, number>;
|
||
warnings: string[];
|
||
}
|
||
|
||
export function buildFromSSR(data: SSRData, profile: SiteProfile): ScanResult {
|
||
const texts: TextMaterial[] = [];
|
||
const images: ImageMaterial[] = [];
|
||
|
||
// 1. 标题(必需)
|
||
texts.push({
|
||
kind: 'title',
|
||
content: data.item.title
|
||
});
|
||
|
||
// 2. 价格
|
||
if (data.price?.priceText) {
|
||
texts.push({
|
||
kind: 'price',
|
||
content: `¥${data.price.priceText}`
|
||
});
|
||
}
|
||
|
||
// 3. 参数表
|
||
const allParams = [
|
||
...(data.params?.basicParamList || []),
|
||
...(data.params?.enhanceParamList || [])
|
||
];
|
||
if (allParams.length > 0) {
|
||
const pairs = allParams
|
||
.filter(p => p.propertyName && p.valueName)
|
||
.map(p => ({ key: p.propertyName, value: p.valueName }));
|
||
|
||
if (pairs.length > 0) {
|
||
texts.push({
|
||
kind: 'params',
|
||
content: pairs.map(p => `${p.key}: ${p.value}`).join('\n'),
|
||
pairs
|
||
});
|
||
}
|
||
}
|
||
|
||
// 4. 主图(item.images)
|
||
let idx = 0;
|
||
(data.item.images || []).forEach((url, i) => {
|
||
if (!url) return;
|
||
const origUrl = toOriginalUrl(url);
|
||
images.push({
|
||
key: `main-${String(i + 1).padStart(3, '0')}`,
|
||
groupKey: 'main',
|
||
groupName: '主图',
|
||
url: origUrl,
|
||
thumbUrl: url,
|
||
index: idx++,
|
||
type: 'img'
|
||
});
|
||
});
|
||
|
||
// 5. SKU 图(skuBase.props[0].values)
|
||
// 淘宝/天猫通常只有一个规格维度(颜色分类),取 props[0]
|
||
const skuProp = data.skuBase?.props?.[0];
|
||
if (skuProp?.values) {
|
||
skuProp.values.forEach((v, i) => {
|
||
if (!v.image) return; // 有些 SKU 没配图(如天猫那个 vid=43699206432)
|
||
const origUrl = toOriginalUrl(v.image);
|
||
images.push({
|
||
key: `sku-${String(i + 1).padStart(3, '0')}`,
|
||
groupKey: 'sku',
|
||
groupName: 'SKU图片',
|
||
variantName: v.name || undefined,
|
||
url: origUrl,
|
||
thumbUrl: v.image,
|
||
index: idx++,
|
||
type: 'img'
|
||
});
|
||
});
|
||
}
|
||
|
||
// 6. 视频(item.videos)
|
||
(data.item.videos || []).forEach((v, i) => {
|
||
if (!v.url) return;
|
||
images.push({
|
||
key: `video-${String(i + 1).padStart(3, '0')}`,
|
||
groupKey: 'video',
|
||
groupName: '视频',
|
||
url: v.url,
|
||
thumbUrl: v.videoThumbnailURL || v.url,
|
||
index: idx++,
|
||
type: 'video'
|
||
});
|
||
});
|
||
|
||
// 统计各组数量
|
||
const stats: Record<string, number> = {};
|
||
for (const img of images) {
|
||
stats[img.groupKey] = (stats[img.groupKey] ?? 0) + 1;
|
||
}
|
||
|
||
// 生成警告
|
||
const warnings: string[] = [];
|
||
if (texts.length === 0) {
|
||
warnings.push('未提取到任何文本');
|
||
}
|
||
if (images.length === 0) {
|
||
warnings.push('未扫描到任何图片/视频');
|
||
}
|
||
// SSR 数据里没有详情图,需要 DOM 补充
|
||
if (stats.detail === undefined) {
|
||
warnings.push('详情图需 DOM 补充:请滚动到页面底部后重新采集');
|
||
}
|
||
|
||
return {
|
||
platform: profile.id,
|
||
itemId: data.item.itemId,
|
||
url: location.href,
|
||
texts,
|
||
images,
|
||
scannedAt: Date.now(),
|
||
stats,
|
||
warnings
|
||
};
|
||
}
|