|
|
/**
|
|
|
* MotherShip 结账选价 / FreightProtect 货值校验
|
|
|
*/
|
|
|
|
|
|
export type MsCoverageKind = "basic" | "freight_protect";
|
|
|
|
|
|
export type MsCheckoutSelection = {
|
|
|
preferredCarrier: string;
|
|
|
coverage: MsCoverageKind;
|
|
|
cargoValueUsd?: number;
|
|
|
};
|
|
|
|
|
|
/** 返回中文错误;null = 通过 */
|
|
|
export function validateMsCheckoutSelection(
|
|
|
input: MsCheckoutSelection,
|
|
|
): string | null {
|
|
|
const carrier = input.preferredCarrier?.trim();
|
|
|
if (!carrier) return "请选择承运商";
|
|
|
if (carrier.length > 128) return "承运商名称过长";
|
|
|
if (input.coverage !== "basic" && input.coverage !== "freight_protect") {
|
|
|
return "保障方案无效";
|
|
|
}
|
|
|
if (input.coverage === "freight_protect") {
|
|
|
const v = Number(input.cargoValueUsd);
|
|
|
if (!Number.isFinite(v) || v <= 0) {
|
|
|
return "选择 FreightProtect 时请填写大于 0 的货物价值(美元)";
|
|
|
}
|
|
|
if (v > 1_000_000) {
|
|
|
return "货物价值超出允许范围";
|
|
|
}
|
|
|
}
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
/** 输入框清洗:允许美元小数(最多两位),并兼容输入过程中的尾随小数点 */
|
|
|
export function sanitizeCargoValueUsdInput(raw: string): string {
|
|
|
let cleaned = raw.replace(/[$,\s]/g, "").replace(/[^\d.]/g, "");
|
|
|
const firstDot = cleaned.indexOf(".");
|
|
|
if (firstDot >= 0) {
|
|
|
cleaned =
|
|
|
cleaned.slice(0, firstDot + 1) +
|
|
|
cleaned.slice(firstDot + 1).replace(/\./g, "");
|
|
|
}
|
|
|
const m = cleaned.match(/^(\d*)(\.?)(\d{0,2})/);
|
|
|
if (!m) return "";
|
|
|
return `${m[1] ?? ""}${m[2] ?? ""}${m[3] ?? ""}`;
|
|
|
}
|
|
|
|
|
|
/** 解析货值(美元);支持小数,四舍五入到分 */
|
|
|
export function parseCargoValueUsd(raw: string): number | null {
|
|
|
const cleaned = raw.replace(/[$,\s]/g, "").trim();
|
|
|
if (!cleaned || cleaned === ".") return null;
|
|
|
const n = Number(cleaned);
|
|
|
if (!Number.isFinite(n) || n <= 0) return null;
|
|
|
const cents = Math.round(n * 100) / 100;
|
|
|
if (!Number.isFinite(cents) || cents <= 0) return null;
|
|
|
return cents;
|
|
|
}
|