printInputOffset.ts 5.01 KB
/**
 * 与 Web 管理端 labelFormDatePreview 一致:templateProductDefaults 中日期/时间/时长存 JSON
 * `{"unit":"Days","value":"2"}`,预览与打印时按 BaseTime 解析为 config.text。
 */
import type { SystemTemplateElementBase } from '../print/types/printer'

const OFFSET_UNITS = new Set([
  'Minutes',
  'Hours',
  'Days',
  'Weeks',
  'Months (30 Day)',
  'Years',
])

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
}

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')
  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 '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(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)
  }
}

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<string, unknown>
    const unit = String(rec.unit ?? rec.Unit ?? '').trim()
    const value = String(rec.value ?? rec.Value ?? '')
    if (!unit || !OFFSET_UNITS.has(unit)) return null
    return { unit, value }
  } catch {
    return null
  }
}

/** 与 Web isDateTimeDataEntryField 对齐(App 侧元素无 typeAdd 时用命名兜底) */
export function isUniAppDateTimeOffsetField (el: SystemTemplateElementBase): boolean {
  const type = String(el.type || '').toUpperCase()
  const cfg = (el.config || {}) as Record<string, unknown>
  if (type === 'TIME' || type === 'DURATION') return true
  if (type !== 'DATE') return false
  const it = String(cfg.inputType ?? cfg.InputType ?? '').toLowerCase()
  if (it === 'datetime' || it === 'date') return true
  const ta = String((el as { typeAdd?: string }).typeAdd ?? '').trim().toLowerCase().replace(/\s+/g, ' ')
  if (ta === 'label_duration date' || ta.includes('duration date')) return true
  const nameHint = `${el.elementName ?? ''} ${el.inputKey ?? ''}`.toLowerCase()
  if (/(durationdate|duration_date|duration\s*date)/.test(nameHint)) return true
  return false
}

/**
 * 将 templateProductDefaults 里单格字符串转为画布用的展示文案(相对 now)。
 * 非 JSON 或无法解析时返回原串(兼容旧版已展开的日期文案)。
 */
export function resolveTemplateDefaultValueForElement (
  el: SystemTemplateElementBase,
  stored: string,
  now: Date,
): string {
  const s = String(stored ?? '').trim()
  if (!s) return ''
  if (!isUniAppDateTimeOffsetField(el)) return s
  const parsed = tryParsePrintInputOffsetStored(s)
  if (!parsed) return s
  const amount = Number(String(parsed.value).trim())
  const unit = parsed.unit || 'Days'
  if (!Number.isFinite(amount) || String(parsed.value).trim() === '') return ''
  const type = String(el.type || '').toUpperCase()
  const cfg = (el.config || {}) as Record<string, unknown>
  const d = applyOffsetToDate(now, amount, unit)
  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}`
}