nativeTemplateElementSupport.ts 5.4 KB
/**
 * Android NativeTemplateCommandBuilder 仅处理:TEXT_*、QRCODE、BARCODE、IMAGE、
 * 以及 border=line 的 BLANK;其余类型在原生路径下会被静默跳过(与画布预览不一致)。
 */
import type {
  LabelTemplateData,
  SystemLabelTemplate,
  SystemTemplateElementBase,
} from './types/printer'
import { applyTemplateData } from './templateRenderer'

function isElementHandledByNativeFastPrinter (el: SystemTemplateElementBase): boolean {
  const type = String(el.type || '').toUpperCase()
  if (type.startsWith('TEXT_')) return true
  if (type === 'NUTRITION') return true
  if (type === 'QRCODE' || type === 'IMAGE') return true
  if (type === 'BARCODE') return true
  if (type === 'BLANK') return true
  return false
}

/**
 * 将 WEIGHT / DATE / TIME / DURATION 转为 TEXT_STATIC(展示文案与合并后的 config.text 一致),
 * LOGO → IMAGE,使同一套模板可走 native printTemplate,避免仅因元素类型名而整页光栅(进度长期停在 ~12–14%)。
 */
export function normalizeTemplateForNativeFastJob (
  template: SystemLabelTemplate,
  data: LabelTemplateData
): SystemLabelTemplate {
  const now = new Date()
  const pad2 = (n: number): string => String(n).padStart(2, '0')
  const formatDateByPreset = (fmt: string, d: Date): string => {
    const yyyy = String(d.getFullYear())
    const yy = yyyy.slice(-2)
    const mm = pad2(d.getMonth() + 1)
    const dd = pad2(d.getDate())
    const hh = pad2(d.getHours())
    const min = pad2(d.getMinutes())
    switch (fmt) {
      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 'YYYY-MM-DD': return `${yyyy}-${mm}-${dd}`
      case 'YYYY-MM-DD HH:mm': return `${yyyy}-${mm}-${dd} ${hh}:${min}`
      case 'HH:mm': return `${hh}:${min}`
      default:
        return String(fmt || '')
          .replace('YYYY', yyyy)
          .replace('YY', yy)
          .replace('MM', mm)
          .replace('DD', dd)
          .replace('HH', hh)
          .replace('mm', min)
    }
  }

  const elements = (template.elements || []).map((el) => {
    const type = String(el.type || '').toUpperCase()
    const config = (el.config || {}) as Record<string, any>
    if (type === 'LOGO') {
      return { ...el, type: 'IMAGE' as typeof el.type }
    }
    if (type === 'WEIGHT' || type === 'DATE' || type === 'TIME' || type === 'DURATION') {
      let text = ''
      /** 与 renderLabelPreviewCanvas.previewTextForElement 一致:后端 AUTO_DB 的 DATE/TIME 常把算好的展示串写在 format 而非 text */
      let raw = String(config.text ?? config.Text ?? '').trim()
      const vst = String(el.valueSourceType || '').toUpperCase()
      const inputType = String(config.inputType ?? config.InputType ?? '').toLowerCase()
      if (type === 'DATE') {
        if (raw) {
          text = applyTemplateData(raw, data)
        } else if (vst === 'PRINT_INPUT' && (inputType === 'date' || inputType === 'datetime')) {
          const fmt = String(config.format ?? config.Format ?? (inputType === 'datetime' ? 'YYYY-MM-DD HH:mm' : 'DD/MM/YYYY')).trim()
          text = fmt
        } else {
          const fmt = String(config.format ?? config.Format ?? 'DD/MM/YYYY').trim() || 'DD/MM/YYYY'
          const offset = Number(config.offsetDays ?? config.OffsetDays ?? 0) || 0
          const d = new Date(now.getTime())
          d.setDate(d.getDate() + offset)
          text = formatDateByPreset(fmt, d)
        }
      } else if (type === 'TIME') {
        if (raw) {
          text = applyTemplateData(raw, data)
        } else if (vst === 'PRINT_INPUT') {
          text = String(config.format ?? config.Format ?? 'HH:mm').trim() || 'HH:mm'
        } else {
          text = formatDateByPreset('HH:mm', now)
        }
      } else if (type === 'DURATION') {
        if (raw) {
          text = applyTemplateData(raw, data)
        } else {
          const unit = String(config.format ?? config.Format ?? 'Days').trim() || 'Days'
          const rawV = config.durationValue ?? config.value ?? config.offsetDays ?? config.DurationValue ?? config.Value ?? config.OffsetDays
          const val = Number.isFinite(Number(rawV)) ? Number(rawV) : 0
          text = `${val} ${unit}`.trim()
        }
      } else if (type === 'WEIGHT') {
        if (!raw) raw = String(config.value ?? config.Value ?? '').trim()
        if (raw) text = applyTemplateData(raw, data)
      }

      if (!text && type === 'WEIGHT') {
        const v = String(config.value ?? config.Value ?? '')
        const u = String(config.unit ?? config.Unit ?? '')
        if (v && u && !v.endsWith(u)) text = `${v}${u}`
        else text = v || u
      }
      return {
        ...el,
        type: 'TEXT_STATIC' as typeof el.type,
        config: { ...config, text, nativeSourceType: type },
      }
    }
    return el
  })
  return { ...template, elements }
}

/** 存在任一原生不支持的元素时,预览打印应走光栅,避免「成功但缺内容/不出纸」与画布不一致 */
export function templateHasUnsupportedNativeFastElements (template: SystemLabelTemplate): boolean {
  for (const el of template.elements || []) {
    if (!isElementHandledByNativeFastPrinter(el)) return true
  }
  return false
}