import type { SystemLabelTemplate, SystemTemplateElementBase } from '../print/types/printer' function asRecord(v: unknown): Record { if (v != null && typeof v === 'object' && !Array.isArray(v)) return v as Record return {} } function normalizeConfig(raw: unknown): Record { if (raw == null) return {} if (typeof raw === 'string') { const t = raw.trim() if (!t) return {} try { const parsed = JSON.parse(t) as unknown if (parsed != null && typeof parsed === 'object' && !Array.isArray(parsed)) { return { ...(parsed as Record) } } } catch { return {} } return {} } const o = asRecord(raw) return { ...o } } const TEXT_PRODUCT_PLACEHOLDERS = new Set(['', '文本', 'text', 'Text', 'TEXT', 'Label', 'label']) /** * Web 端设计器里 TEXT_PRODUCT 若为 FIXED 且占位「文本」,预览页用当前商品名覆盖(与列表传入的 productName 一致)。 */ export function overlayProductNameOnPreviewTemplate( template: SystemLabelTemplate, productName: string | undefined ): SystemLabelTemplate { const name = (productName ?? '').trim() if (!name) return template const elements = (template.elements || []).map((el) => { const type = String(el.type || '').toUpperCase() if (type !== 'TEXT_PRODUCT') return el const raw = String((el.config as any)?.text ?? (el.config as any)?.Text ?? '').trim() if (raw && !TEXT_PRODUCT_PLACEHOLDERS.has(raw)) return el return { ...el, config: { ...(el.config || {}), text: name }, } }) return { ...template, elements } } /** * 解析接口 `labelSizeText`(如 `2"x2"`、`6.00x4.00cm`、`2.00*2.00"`)。 * 无单位后缀时默认 **inch**(与历史 `2"x2"` 一致)。 */ export function parseLabelSizeText( raw: string | null | undefined ): { width: number; height: number; unit: 'inch' | 'mm' | 'cm' | 'px' } | null { if (raw == null) return null let s = String(raw).trim() if (!s) return null s = s.replace(/["'\u201C\u201D\u2018\u2019\u2032\u2033]/g, '').replace(/\s+/g, '').toLowerCase() const m = s.match(/^(\d+(?:\.\d+)?)[*×x](\d+(?:\.\d+)?)(mm|cm|inch|in|px)?$/i) if (!m) return null const w = Number(m[1]) const h = Number(m[2]) if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) return null const suf = (m[3] || '').toLowerCase() let unit: 'inch' | 'mm' | 'cm' | 'px' = 'inch' if (suf === 'mm') unit = 'mm' else if (suf === 'cm') unit = 'cm' else if (suf === 'px') unit = 'px' else if (suf === 'in' || suf === 'inch') unit = 'inch' return { width: w, height: h, unit } } /** * 用 `labelSizeText` 覆盖模板物理宽高(解析失败则保持原模板)。 * 注意:App 预览画布应以接口 **template.width / height / unit** 为准(见 preview 页),勿与本函数同时叠加强制覆盖。 */ export function applyLabelSizeTextToTemplate( template: SystemLabelTemplate, labelSizeText: string | null | undefined ): SystemLabelTemplate { const parsed = parseLabelSizeText(labelSizeText) if (!parsed) return template return { ...template, unit: parsed.unit, width: parsed.width, height: parsed.height, } } /** * 从预览接口响应中取出 `templateProductDefaultValues`(elementId → 字符串,兼容 PascalCase / 嵌套 data)。 */ export function extractTemplateProductDefaultValuesFromPreviewPayload(payload: unknown): Record { const tryLayer = (layer: unknown): Record | null => { if (layer == null || typeof layer !== 'object' || Array.isArray(layer)) return null const L = layer as Record const raw = L.templateProductDefaultValues ?? L.TemplateProductDefaultValues if (raw == null || typeof raw !== 'object' || Array.isArray(raw)) return null const out: Record = {} for (const [k, v] of Object.entries(raw as Record)) { if (v == null) out[k] = '' else if (typeof v === 'string') out[k] = v else if (typeof v === 'number' || typeof v === 'boolean') out[k] = String(v) else out[k] = JSON.stringify(v) } return out } if (payload == null || typeof payload !== 'object') return {} const r = payload as Record const nested = r.data ?? r.Data const inner = nested != null && typeof nested === 'object' && !Array.isArray(nested) ? (nested as Record) : null const a = inner ? tryLayer(inner) : null if (a && Object.keys(a).length > 0) return a const b = tryLayer(r) if (b && Object.keys(b).length > 0) return b const c = tryLayer(payload) return c && Object.keys(c).length > 0 ? c : {} } /** * 将平台录入的默认值合并进模板元素 config,供画布预览(键与 elements[].id 一致)。 */ export function applyTemplateProductDefaultValuesToTemplate( template: SystemLabelTemplate, defaults: Record ): SystemLabelTemplate { const keys = Object.keys(defaults) if (!keys.length) return template const elements = (template.elements || []).map((el) => { const byName = (el.elementName ?? '').trim() const v = defaults[el.id] ?? (byName ? defaults[byName] : undefined) if (v === undefined) return el const type = String(el.type || '').toUpperCase() const cfg = { ...(el.config || {}) } as Record if (type === 'IMAGE' || type === 'LOGO') { cfg.src = v cfg.url = v cfg.Src = v cfg.Url = v return { ...el, config: cfg } } if (type === 'BARCODE' || type === 'QRCODE') { cfg.data = v cfg.Data = v return { ...el, config: cfg } } if (type === 'WEIGHT') { cfg.value = v cfg.Value = v cfg.text = v cfg.Text = v return { ...el, config: cfg } } cfg.text = v cfg.Text = v return { ...el, config: cfg } }) return { ...template, elements } } /** * 将接口 8.2 返回的 template(或整段 DTO)规范为 SystemLabelTemplate,供打印适配器与预览画布使用。 */ export function normalizeLabelTemplateFromPreviewApi(payload: unknown): SystemLabelTemplate | null { if (payload == null || typeof payload !== 'object') return null const root = payload as Record const t = asRecord(root.template ?? root.Template ?? root) const elementsRaw = t.elements ?? t.Elements if (!Array.isArray(elementsRaw)) return null const elements: SystemTemplateElementBase[] = (elementsRaw as unknown[]).map((el, index) => { const e = asRecord(el) const cfg = normalizeConfig(e.config ?? e.ConfigJson ?? e.configJson) const type = String(e.type ?? e.elementType ?? e.ElementType ?? 'TEXT_STATIC') const vst = e.valueSourceType ?? e.ValueSourceType const ik = e.inputKey ?? e.InputKey const en = e.elementName ?? e.ElementName return { id: String(e.id ?? e.Id ?? `el-${index}`), type, x: Number(e.x ?? e.posX ?? e.PosX ?? 0), y: Number(e.y ?? e.posY ?? e.PosY ?? 0), width: Number(e.width ?? e.Width ?? 0), height: Number(e.height ?? e.Height ?? 0), rotation: String(e.rotation ?? e.Rotation ?? 'horizontal') as 'horizontal' | 'vertical', border: String(e.border ?? e.BorderType ?? e.borderType ?? 'none'), config: cfg as Record, zIndex: Number(e.zIndex ?? e.ZIndex ?? 0), orderNum: Number(e.orderNum ?? e.OrderNum ?? index), valueSourceType: vst != null ? String(vst) : undefined, inputKey: ik != null && String(ik).trim() !== '' ? String(ik).trim() : undefined, elementName: en != null && String(en).trim() !== '' ? String(en).trim() : undefined, } as SystemTemplateElementBase & { zIndex: number; orderNum: number } }) const unitRaw = String(t.unit ?? t.Unit ?? 'inch').toLowerCase() const unit = (unitRaw === 'mm' || unitRaw === 'cm' || unitRaw === 'px' ? unitRaw : 'inch') as | 'inch' | 'mm' | 'cm' | 'px' return { id: String(t.id ?? t.Id ?? 'preview'), name: String(t.name ?? t.Name ?? 'Label'), labelType: String(t.labelType ?? t.LabelType ?? ''), unit, width: Number(t.width ?? t.Width ?? 2), height: Number(t.height ?? t.Height ?? 2), appliedLocation: String(t.appliedLocation ?? t.AppliedLocation ?? 'ALL'), showRuler: !!(t.showRuler ?? t.ShowRuler), showGrid: !!(t.showGrid ?? t.ShowGrid), elements, } } export function sortElementsForPreview( elements: SystemTemplateElementBase[] ): SystemTemplateElementBase[] { return [...elements].sort((a, b) => { const za = Number((a as any).zIndex ?? 0) const zb = Number((b as any).zIndex ?? 0) if (za !== zb) return za - zb const oa = Number((a as any).orderNum ?? 0) const ob = Number((b as any).orderNum ?? 0) return oa - ob }) }