67 lines
2.3 KiB
TypeScript
67 lines
2.3 KiB
TypeScript
/**
|
||
* 根据运单号前缀推断快递公司(用于发货表单自动选中)
|
||
* 匹配关键字同时支持公司名与 code(如 shunfeng)
|
||
*/
|
||
|
||
export type ExpressMatchOption = {
|
||
id?: number;
|
||
name?: string;
|
||
code?: string;
|
||
};
|
||
|
||
/** 常见单号前缀 → 公司名 / code 关键字(按前缀长度降序匹配,避免短前缀误伤) */
|
||
const PREFIX_RULES: Array<{ prefixes: string[]; keywords: string[] }> = [
|
||
{ prefixes: ['SF'], keywords: ['顺丰', 'shunfeng', 'sf'] },
|
||
{ prefixes: ['ZTO', 'ZT'], keywords: ['中通', 'zhongtong', 'zto'] },
|
||
{ prefixes: ['STO'], keywords: ['申通', 'shentong', 'sto'] },
|
||
{ prefixes: ['YTO', 'YT'], keywords: ['圆通', 'yuantong', 'yto'] },
|
||
{ prefixes: ['YD'], keywords: ['韵达', 'yunda', 'yd'] },
|
||
{ prefixes: ['JD'], keywords: ['京东', 'jd', 'jingdong'] },
|
||
{ prefixes: ['JT'], keywords: ['极兔', 'jitu', 'jt'] },
|
||
{ prefixes: ['EMS', 'E'], keywords: ['ems', '邮政'] },
|
||
{ prefixes: ['HHTT', 'HT'], keywords: ['百世', 'baishi', 'huitong'] },
|
||
{ prefixes: ['UC'], keywords: ['优速', 'uc'] },
|
||
{ prefixes: ['DBL'], keywords: ['德邦', 'debang', 'dbl'] },
|
||
];
|
||
|
||
/**
|
||
* 按运单号前缀在 options 中找最可能的快递公司
|
||
* @returns 匹配到的 option,未匹配返回 null
|
||
*/
|
||
export function matchExpressByTrackingNo(
|
||
trackingNo: string | undefined | null,
|
||
options: ExpressMatchOption[],
|
||
): ExpressMatchOption | null {
|
||
const no = String(trackingNo || '')
|
||
.trim()
|
||
.toUpperCase()
|
||
.replace(/\s+/g, '');
|
||
if (!no || !Array.isArray(options) || !options.length) {
|
||
return null;
|
||
}
|
||
const sortedRules = [...PREFIX_RULES].sort(
|
||
(a, b) =>
|
||
Math.max(...b.prefixes.map((p) => p.length)) -
|
||
Math.max(...a.prefixes.map((p) => p.length)),
|
||
);
|
||
let matchedKeywords: string[] | null = null;
|
||
for (const rule of sortedRules) {
|
||
if (rule.prefixes.some((p) => no.startsWith(p))) {
|
||
matchedKeywords = rule.keywords;
|
||
break;
|
||
}
|
||
}
|
||
if (!matchedKeywords) {
|
||
return null;
|
||
}
|
||
const keywords = matchedKeywords.map((k) => k.toLowerCase());
|
||
for (const opt of options) {
|
||
const name = String(opt.name || '').toLowerCase();
|
||
const code = String(opt.code || '').toLowerCase();
|
||
if (keywords.some((k) => name.includes(k) || code === k || code.includes(k))) {
|
||
return opt;
|
||
}
|
||
}
|
||
return null;
|
||
}
|