export type WeightUnit = "kg" | "g" | "lb"; const UNIT_FACTORS: Record = { kg: 1, g: 1000, lb: 2.20462262, }; /** * 将内部统一保存的千克值转换为界面单位,并固定显示两位小数。 */ export function formatWeight(valueKg: number, unit: WeightUnit) { const safeValue = Number.isFinite(valueKg) ? Math.max(0, valueKg) : 0; return (safeValue * UNIT_FACTORS[unit]).toFixed(2); } /** * 把 ISO 时间转换为终端文件名使用的本地时间格式:YYYYMMDDHHmmss。 */ export function createFileTimestamp(iso: string) { const date = new Date(iso); const pad = (value: number) => value < 10 ? `0${value}` : String(value); return [ date.getFullYear(), pad(date.getMonth() + 1), pad(date.getDate()), pad(date.getHours()), pad(date.getMinutes()), pad(date.getSeconds()), ].join(""); } /** * 兼容电子秤服务常见的消息格式: * - 纯数字,例如 `2.49` * - JSON,例如 `{"weight": 2490, "unit": "g"}` * - JSON 千克字段,例如 `{"weightKg": 2.49}` * * 返回值统一为千克;无效值、负数或不支持的单位返回 null。 */ export function parseScaleMessage(message: string): number | null { const trimmed = message.trim(); if (!trimmed) return null; let payload: unknown = trimmed; try { payload = JSON.parse(trimmed); } catch { // 非 JSON 消息继续按纯数字处理。 } if (typeof payload === "number" || typeof payload === "string") { const value = Number(payload); return Number.isFinite(value) && value >= 0 ? value : null; } if (!payload || typeof payload !== "object") return null; const record = payload as Record; const rawValue = record.weightKg ?? record.weight ?? record.value; const value = Number(rawValue); if (!Number.isFinite(value) || value < 0) return null; const unit = String(record.unit ?? (record.weightKg != null ? "kg" : "kg")).toLowerCase(); if (unit === "kg") return value; if (unit === "g") return value / 1000; if (unit === "lb" || unit === "lbs") return value / UNIT_FACTORS.lb; return null; } export type StabilityResult = { stable: boolean; progress: number; }; /** * 根据连续采样窗口判断重量是否稳定。 * 至少需要 6 个有效样本,样本极差小于 8 克时判定稳定。 */ export function evaluateStability(samples: number[]): StabilityResult { // 不使用 Array.prototype.at,确保兼容终端内置的 Chrome 52 WebView。 const latest = samples.length ? samples[samples.length - 1] : 0; if (samples.length < 6 || latest < 0.01) { return { stable: false, progress: Math.min(85, samples.length * 14), }; } const spread = Math.max(...samples) - Math.min(...samples); const stable = spread < 0.008; return { stable, progress: stable ? 100 : Math.max(25, 90 - spread * 500), }; }