|
|
/**
|
|
|
* Flock 登录态「常用货件」:按客户维度存浏览器 localStorage,供搜索快速回填。
|
|
|
*/
|
|
|
|
|
|
export const FLOCK_SAVED_FREIGHT_MAX = 40;
|
|
|
|
|
|
export type FlockSavedFreightPreset = {
|
|
|
id: string;
|
|
|
/** 列表/搜索显示名 */
|
|
|
name: string;
|
|
|
description: string;
|
|
|
quantity: string;
|
|
|
packagingType: string;
|
|
|
lengthIn: string;
|
|
|
widthIn: string;
|
|
|
heightIn: string;
|
|
|
totalWeightLb: string;
|
|
|
freightClass: string;
|
|
|
stackable: boolean;
|
|
|
turnable: boolean;
|
|
|
updatedAt: string;
|
|
|
};
|
|
|
|
|
|
export type FlockSavedFreightSource = {
|
|
|
description: string;
|
|
|
quantity: string;
|
|
|
packagingType: string;
|
|
|
lengthIn: string;
|
|
|
widthIn: string;
|
|
|
heightIn: string;
|
|
|
totalWeightLb: string;
|
|
|
freightClass: string;
|
|
|
stackable: boolean;
|
|
|
turnable: boolean;
|
|
|
};
|
|
|
|
|
|
export function flockSavedFreightStorageKey(customerId: string): string {
|
|
|
const id = customerId.trim() || "anon";
|
|
|
return `chajia.flock.savedFreight.v1:${id}`;
|
|
|
}
|
|
|
|
|
|
function isPreset(v: unknown): v is FlockSavedFreightPreset {
|
|
|
if (!v || typeof v !== "object") return false;
|
|
|
const o = v as Record<string, unknown>;
|
|
|
return (
|
|
|
typeof o.id === "string" &&
|
|
|
typeof o.name === "string" &&
|
|
|
typeof o.description === "string"
|
|
|
);
|
|
|
}
|
|
|
|
|
|
export function parseFlockSavedFreightList(
|
|
|
raw: string | null,
|
|
|
): FlockSavedFreightPreset[] {
|
|
|
if (!raw) return [];
|
|
|
try {
|
|
|
const data = JSON.parse(raw) as unknown;
|
|
|
if (!Array.isArray(data)) return [];
|
|
|
return data.filter(isPreset).slice(0, FLOCK_SAVED_FREIGHT_MAX);
|
|
|
} catch {
|
|
|
return [];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
export function loadFlockSavedFreight(
|
|
|
customerId: string,
|
|
|
storage: Pick<Storage, "getItem"> = globalThis.localStorage,
|
|
|
): FlockSavedFreightPreset[] {
|
|
|
try {
|
|
|
return parseFlockSavedFreightList(
|
|
|
storage.getItem(flockSavedFreightStorageKey(customerId)),
|
|
|
);
|
|
|
} catch {
|
|
|
return [];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
export function persistFlockSavedFreight(
|
|
|
customerId: string,
|
|
|
list: FlockSavedFreightPreset[],
|
|
|
storage: Pick<Storage, "setItem"> = globalThis.localStorage,
|
|
|
): void {
|
|
|
const trimmed = list.slice(0, FLOCK_SAVED_FREIGHT_MAX);
|
|
|
storage.setItem(
|
|
|
flockSavedFreightStorageKey(customerId),
|
|
|
JSON.stringify(trimmed),
|
|
|
);
|
|
|
}
|
|
|
|
|
|
export function filterFlockSavedFreight(
|
|
|
list: FlockSavedFreightPreset[],
|
|
|
query: string,
|
|
|
): FlockSavedFreightPreset[] {
|
|
|
const q = query.trim().toLowerCase();
|
|
|
if (!q) return list;
|
|
|
return list.filter(
|
|
|
(p) =>
|
|
|
p.name.toLowerCase().includes(q) ||
|
|
|
p.description.toLowerCase().includes(q),
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/** 同名(忽略大小写)覆盖;新条目插到最前 */
|
|
|
export function upsertFlockSavedFreight(
|
|
|
list: FlockSavedFreightPreset[],
|
|
|
preset: FlockSavedFreightPreset,
|
|
|
): FlockSavedFreightPreset[] {
|
|
|
const nameKey = preset.name.trim().toLowerCase();
|
|
|
const without = list.filter(
|
|
|
(p) => p.id !== preset.id && p.name.trim().toLowerCase() !== nameKey,
|
|
|
);
|
|
|
return [preset, ...without].slice(0, FLOCK_SAVED_FREIGHT_MAX);
|
|
|
}
|
|
|
|
|
|
export function removeFlockSavedFreight(
|
|
|
list: FlockSavedFreightPreset[],
|
|
|
id: string,
|
|
|
): FlockSavedFreightPreset[] {
|
|
|
return list.filter((p) => p.id !== id);
|
|
|
}
|
|
|
|
|
|
export function buildFlockSavedFreightPreset(
|
|
|
source: FlockSavedFreightSource,
|
|
|
name: string,
|
|
|
id = `sf-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
|
|
|
): FlockSavedFreightPreset | null {
|
|
|
const desc = source.description.trim();
|
|
|
const displayName = name.trim() || desc;
|
|
|
if (!displayName && !desc) return null;
|
|
|
return {
|
|
|
id,
|
|
|
name: displayName || desc,
|
|
|
description: desc || displayName,
|
|
|
quantity: source.quantity,
|
|
|
packagingType: source.packagingType,
|
|
|
lengthIn: source.lengthIn,
|
|
|
widthIn: source.widthIn,
|
|
|
heightIn: source.heightIn,
|
|
|
totalWeightLb: source.totalWeightLb,
|
|
|
freightClass: source.freightClass,
|
|
|
stackable: Boolean(source.stackable),
|
|
|
turnable: Boolean(source.turnable),
|
|
|
updatedAt: new Date().toISOString(),
|
|
|
};
|
|
|
}
|