Your Name 478562c312
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: migrate product planning workspace
2026-03-26 16:38:48 +08:00

160 lines
3.9 KiB
TypeScript
Raw 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.

import type { ProductPlanningPlatform } from "./types";
const HEADER_SANITIZE_PATTERN = /[\s_\-./\\()[\]{}::,,;;'"`~!@#$%^&*+|?<>]/g;
export function normalizeHeader(value: unknown) {
return String(value || "")
.replace(/^\ufeff/, "")
.trim()
.toLowerCase()
.replace(HEADER_SANITIZE_PATTERN, "");
}
export function normalizeText(value: unknown) {
return String(value || "").replace(/^\ufeff/, "").trim();
}
export function parseRangeValue(value: unknown): number | null {
if (value === null || value === undefined || value === "") {
return null;
}
const text = String(value).trim();
if (!text) return null;
const rangeMatch = text.match(
/^(\d+(?:\.\d+)?)\s*[-~~至到]\s*(\d+(?:\.\d+)?)$/
);
if (rangeMatch) {
return Math.min(Number.parseFloat(rangeMatch[1]), Number.parseFloat(rangeMatch[2]));
}
const cleaned = text.replace(/[,,%%\s]/g, "");
const parsed = Number.parseFloat(cleaned);
return Number.isFinite(parsed) ? parsed : null;
}
export function parseInteger(value: unknown, fallback = 0) {
const parsed = Number.parseInt(String(value || "").trim(), 10);
return Number.isFinite(parsed) ? parsed : fallback;
}
export function ensureHttpsUrl(value: unknown) {
const text = String(value || "").trim();
if (!text) return "";
if (text.startsWith("//")) return `https:${text}`;
if (/^https?:\/\//i.test(text)) return text;
return `https://${text}`;
}
export function splitUrlCell(value: unknown) {
const text = String(value || "").trim();
if (!text) return [] as string[];
return text
.split(/[\n,,;;\s]+/)
.map((item) => ensureHttpsUrl(item))
.filter((item) => /^https?:\/\//i.test(item));
}
export function extractTaobaoItemId(value: string) {
const input = String(value || "").trim();
if (!input) return null;
if (/^\d+$/.test(input)) return input;
const patterns = [
/item\.taobao\.com\/item\.htm.*?[?&]id=(\d+)/i,
/detail\.tmall\.com\/item\.htm.*?[?&]id=(\d+)/i,
/a\.m\.taobao\.com\/i(\d+)/i,
/[?&]id=(\d+)/i,
/(\d{10,})/,
];
for (const pattern of patterns) {
const matched = input.match(pattern);
if (matched?.[1]) {
return matched[1];
}
}
return null;
}
export function extractJdItemId(value: string) {
const input = String(value || "").trim();
if (!input) return null;
const explicitPrefix = input.match(/^jd[\s::]+(\d{6,})$/i);
if (explicitPrefix?.[1]) {
return explicitPrefix[1];
}
const patterns = [
/item\.jd\.com\/(\d+)\.html/i,
/sku=(\d+)/i,
/\/product\/(\d+)\.html/i,
/[?&]sku=(\d+)/i,
];
for (const pattern of patterns) {
const matched = input.match(pattern);
if (matched?.[1]) {
return matched[1];
}
}
return null;
}
export function parseMarketplaceItemReference(value: string): {
platform: ProductPlanningPlatform;
itemId: string;
} | null {
const input = String(value || "").trim();
if (!input) return null;
const jdItemId = extractJdItemId(input);
if (jdItemId) {
return {
platform: "jd",
itemId: jdItemId,
};
}
const taobaoItemId = extractTaobaoItemId(input);
if (taobaoItemId) {
return {
platform: "taobao",
itemId: taobaoItemId,
};
}
return null;
}
export function uniqueBy<T>(items: T[], getKey: (item: T) => string) {
const seen = new Set<string>();
const result: T[] = [];
for (const item of items) {
const key = getKey(item);
if (!key || seen.has(key)) continue;
seen.add(key);
result.push(item);
}
return result;
}
export function truncateText(value: unknown, maxLength: number) {
const text = normalizeText(value);
if (text.length <= maxLength) return text;
return `${text.slice(0, maxLength)}...`;
}
export function parseOfficialFlag(value: unknown) {
const text = normalizeText(value).toLowerCase();
return ["true", "1", "yes", "y", "是", "官方"].includes(text);
}