import type { LabelElement } from "../types/labelTemplate"; import { canonicalElementType, isDateTimeDataEntryField } from "../types/labelTemplate"; /** * 标签创建/编辑表单:相对时间偏移 + 按模板 format 预览(与 LabelCanvas formatDateByPreset 对齐) */ export const LABEL_FORM_OFFSET_UNITS = [ "Minutes", "Hours", "Days", "Weeks", "Months (30 Day)", "Years", ] as const; export type LabelFormOffsetUnit = (typeof LABEL_FORM_OFFSET_UNITS)[number]; export function applyOffsetToDate(base: Date, amount: number, unit: string): Date { const d = new Date(base.getTime()); const u = String(unit ?? "").trim(); switch (u) { case "Minutes": d.setMinutes(d.getMinutes() + amount); break; case "Hours": d.setHours(d.getHours() + amount); break; case "Days": d.setDate(d.getDate() + amount); break; case "Weeks": d.setDate(d.getDate() + amount * 7); break; case "Months (30 Day)": d.setDate(d.getDate() + amount * 30); break; case "Years": d.setFullYear(d.getFullYear() + amount); break; default: d.setDate(d.getDate() + amount); } return d; } /** 与 LabelCanvas 中 formatDateByPreset 一致 */ export function formatDateByPreset(format: string, date: Date): string { const yyyy = String(date.getFullYear()); const yy = yyyy.slice(-2); const mm = String(date.getMonth() + 1).padStart(2, "0"); const dd = String(date.getDate()).padStart(2, "0"); const hh = String(date.getHours()).padStart(2, "0"); const min = String(date.getMinutes()).padStart(2, "0"); const monthLong = date.toLocaleString("en-US", { month: "long" }).toUpperCase(); const dayLong = date.toLocaleString("en-US", { weekday: "long" }).toUpperCase(); const dayShort = date.toLocaleString("en-US", { weekday: "short" }).toUpperCase(); const monthShort = date.toLocaleString("en-US", { month: "short" }).toUpperCase(); switch (format) { case "DD/MM/YYYY": return `${dd}/${mm}/${yyyy}`; case "MM/DD/YYYY": return `${mm}/${dd}/${yyyy}`; case "DD/MM/YY": return `${dd}/${mm}/${yy}`; case "MM/DD/YY": return `${mm}/${dd}/${yy}`; case "MM/YY": return `${mm}/${yy}`; case "MM/DD": return `${mm}/${dd}`; case "MM": return mm; case "DD": return dd; case "YY": return yy; case "FULLY DAY(WEDNESDAY)": return dayLong; case "DAY (WED)": return dayShort; case "MONTH (DECEMBER)": return monthLong; case "YEAR (2025)": return yyyy; case "DD MONTH YEAR (25 DECEMBER 2025)": return `${dd} ${monthLong} ${yyyy}`; default: return format .replace(/YYYY/g, yyyy) .replace(/YY/g, yy) .replace(/MM/g, mm) .replace(/DD/g, dd) .replace(/HH/g, hh) .replace(/mm/g, min); } } const OFFSET_UNIT_SET = new Set(LABEL_FORM_OFFSET_UNITS as unknown as string[]); export type NormalizedLabelFormOffset = | { kind: "zero" } | { kind: "amount"; amount: number; storeValue: string } | { kind: "invalid" }; /** * 创建/编辑标签表单:日期时间类偏移的**空串**或**数值 0**(含 0、0.0、+0 等)均视为相对基准偏移 0; * 预览即「当前基准时刻」;落库建议用 {@link serializePrintInputOffset}(unit, "0")。 */ export function normalizeLabelFormOffsetInput(valueRaw: string | undefined): NormalizedLabelFormOffset { const t = String(valueRaw ?? "").trim(); if (t === "") return { kind: "zero" }; const n = Number(t); if (!Number.isFinite(n)) return { kind: "invalid" }; if (n === 0) return { kind: "zero" }; return { kind: "amount", amount: n, storeValue: t }; } /** * 模板 productDefault 中日期/时间/时长录入:存 `{"unit":"Days","value":"2"}`,供 App 与打印按 BaseTime 解析。 */ export function serializePrintInputOffset(unit: string, value: string): string { const u = String(unit ?? "").trim() || "Days"; return JSON.stringify({ unit: u, value: String(value ?? "") }); } export function tryParsePrintInputOffsetStored( raw: string | null | undefined, ): { unit: string; value: string } | null { const t = String(raw ?? "").trim(); if (!t.startsWith("{")) return null; try { const o = JSON.parse(t) as unknown; if (o == null || typeof o !== "object" || Array.isArray(o)) return null; const rec = o as Record; const unit = String(rec.unit ?? rec.Unit ?? "").trim(); const value = String(rec.value ?? rec.Value ?? ""); if (!unit || !OFFSET_UNIT_SET.has(unit)) return null; return { unit, value }; } catch { return null; } } /** 批量录入格:无 JSON 时纯数字按 Days 预填,便于兼容旧 duration 整数 */ export function offsetFieldUiStateFromStored(stored: string | undefined): { unit: string; value: string } { const p = tryParsePrintInputOffsetStored(stored); if (p) return p; const t = String(stored ?? "").trim(); if (/^\d+$/.test(t)) return { unit: "Days", value: t }; return { unit: "Days", value: "" }; } /** * 将落库的 JSON(或旧版纯文案)解析为画布/预览用的展示字符串(相对 base 时间)。 */ export function resolveStoredPrintValueToDisplayText( el: LabelElement, stored: string, base: Date, ): string { const s = String(stored ?? "").trim(); if (!s) return ""; if (!isDateTimeDataEntryField(el)) return s; const parsed = tryParsePrintInputOffsetStored(s); if (!parsed) return s; const unit = parsed.unit || "Days"; const norm = normalizeLabelFormOffsetInput(parsed.value); if (norm.kind === "invalid") return s; const amount = norm.kind === "zero" ? 0 : norm.amount; const type = canonicalElementType(el.type); const d = applyOffsetToDate(base, amount, unit); const cfg = el.config as Record; if (type === "DATE") { const it = String(cfg.inputType ?? cfg.InputType ?? "").toLowerCase(); const format = (typeof cfg.format === "string" && cfg.format.trim() ? cfg.format : typeof cfg.Format === "string" && cfg.Format.trim() ? cfg.Format : it === "datetime" ? "YYYY-MM-DD HH:mm" : "DD/MM/YYYY") ?? "DD/MM/YYYY"; return formatDateByPreset(format, d); } if (type === "TIME") { return formatDateByPreset("HH:mm", d); } return `${amount} ${unit}`; }