gongxiangp2/lib/spu-parser.ts
chenpei 9cd3c0f231 feat: 任务详情无条码开关 + 上传面板信息密度优化 + 历史导入/脚本补全
- 任务详情上传支持"本批素材无条码"开关,跳过条码必填校验
- SKU卡片/主图网格从4列改5列,图片高度压缩,去掉冗余标签和提示
- 详情图/PSD上传区去掉大占位框,改成紧凑按钮+标签
- 补充历史导入相关脚本和工具文件

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-11 10:01:08 +08:00

188 lines
4.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* SPU 型号解析器
* 从 SPU 型号中自动识别店铺和品类
*
* SPU 格式说明:
* - GBPM-SN0204 → 店铺: 朴美, 品类: 收纳柜斗柜角柜飘窗
* - XYL-BJ0301 → 店铺: 橡野里, 品类: 边几
* - SN0204 → 店铺: 用户手动选, 品类: 收纳柜斗柜角柜飘窗
*/
/**
* SPU 解析结果
*/
export interface SPUParseResult {
/** 是否成功解析到店铺 */
shopMatched: boolean
/** 店铺名称(如果识别成功) */
shopName: string
/** 是否成功解析到品类 */
categoryMatched: boolean
/** 品类名称(如果识别成功) */
categoryName: string
/** 品类代码(如 SN, XG 等) */
categoryCode: string
}
/**
* 店铺前缀映射表
* 前缀 → 店铺名称
*/
const SHOP_PREFIX_MAP: Record<string, string> = {
'GBPM': '朴美',
'GBSFY': '索菲亚',
'GBGJ': '顾家',
'GBJP': '顾家',
'XYL': '橡野里',
}
/**
* 品类代码映射表
* 代码 → 品类名称
*/
const CATEGORY_CODE_MAP: Record<string, string> = {
// 收纳类
'SN': '收纳柜斗柜角柜飘窗',
'DG': '收纳柜斗柜角柜飘窗',
// 柜类
'XG': '鞋柜',
'BG': '边柜',
'SG': '书柜',
'CBG': '餐边柜',
// 桌类
'CZ': '餐桌',
'SZ': '书桌',
'DNZ': '电脑桌',
'SJZ': '电脑桌',
'ZDZ': '电脑桌',
'CBZ': '电脑桌',
// 椅凳类
'CY': '椅子',
'AD': '椅子',
'SZD': '椅子',
'HXD': '换鞋凳',
// 架类
'SJ': '花架书架',
'ZSJ': '花架书架',
'ZWJ': '置物架',
'CFZW': '置物架',
'YMJ': '衣帽架',
// 几类
'BJ': '边几',
'CJ': '电视柜茶几',
'DSG': '电视柜茶几',
'CTG': '床头柜',
// 其他
'CYJ': '穿衣镜',
'SZT': '化妆台',
'XJ': '鞋架',
'LRSF': '沙发',
}
/**
* 获取所有店铺前缀(按长度降序排列,优先匹配长前缀)
*/
const SHOP_PREFIXES = Object.keys(SHOP_PREFIX_MAP).sort((a, b) => b.length - a.length)
/**
* 获取所有品类代码(按长度降序排列,优先匹配长代码)
*/
const CATEGORY_CODES = Object.keys(CATEGORY_CODE_MAP).sort((a, b) => b.length - a.length)
/**
* 解析 SPU 型号,提取店铺和品类信息
*
* @param spu SPU 型号字符串
* @returns 解析结果
*
* @example
* parseSPU('GBPM-SN0204')
* // { shopMatched: true, shopName: '朴美', categoryMatched: true, categoryName: '收纳柜斗柜角柜飘窗', categoryCode: 'SN' }
*
* parseSPU('XYL-BJ0301')
* // { shopMatched: true, shopName: '橡野里', categoryMatched: true, categoryName: '边几', categoryCode: 'BJ' }
*
* parseSPU('SN0204')
* // { shopMatched: false, shopName: '', categoryMatched: true, categoryName: '收纳柜斗柜角柜飘窗', categoryCode: 'SN' }
*/
export function parseSPU(spu: string): SPUParseResult {
const result: SPUParseResult = {
shopMatched: false,
shopName: '',
categoryMatched: false,
categoryName: '',
categoryCode: '',
}
if (!spu || typeof spu !== 'string') {
return result
}
// 统一转大写处理
const upperSPU = spu.toUpperCase().trim()
if (!upperSPU) {
return result
}
let remainingPart = upperSPU
// 1. 尝试匹配店铺前缀(格式:PREFIX-...)
for (const prefix of SHOP_PREFIXES) {
if (upperSPU.startsWith(prefix + '-')) {
result.shopMatched = true
result.shopName = SHOP_PREFIX_MAP[prefix]
remainingPart = upperSPU.substring(prefix.length + 1) // 去掉前缀和连字符
break
}
}
// 2. 尝试匹配品类代码(格式:CODE + 数字)
for (const code of CATEGORY_CODES) {
// 品类代码后面必须跟数字
const regex = new RegExp(`^${code}\\d`)
if (regex.test(remainingPart)) {
result.categoryMatched = true
result.categoryName = CATEGORY_CODE_MAP[code]
result.categoryCode = code
break
}
}
return result
}
/**
* 获取所有可用的店铺前缀列表(用于提示)
*/
export function getShopPrefixes(): string[] {
return Object.keys(SHOP_PREFIX_MAP)
}
/**
* 获取所有可用的品类代码列表(用于提示)
*/
export function getCategoryCodes(): string[] {
return Object.keys(CATEGORY_CODE_MAP)
}
/**
* 获取店铺前缀映射表(用于调试或显示)
*/
export function getShopPrefixMap(): Record<string, string> {
return { ...SHOP_PREFIX_MAP }
}
/**
* 获取品类代码映射表(用于调试或显示)
*/
export function getCategoryCodeMap(): Record<string, string> {
return { ...CATEGORY_CODE_MAP }
}