const FONT_SIZE_MAP = { 1: '14px', 2: '18px', 3: '28px', } const escapeHtml = (value) => { return String(value ?? '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') } const replacePlaceholders = (content, data) => { return String(content || '').replace(/\{\{(\w+)\}\}/g, (_, key) => { const val = data[key] return val !== undefined && val !== null ? String(val) : '' }) } export const PLACEHOLDER_TEXT_CONTENTS = ['{{title}}', '{{ticketNo}}'] export const isPlaceholderTextSection = (section) => { if (section?.type !== 'text') return false const content = String(section.content || '').trim() return PLACEHOLDER_TEXT_CONTENTS.includes(content) } const normalizeTextSection = (section) => { const normalized = { type: 'text', content: String(section.content || ''), fontSize: Number(section.fontSize) || 1, align: section.align || 'left', } if (section.bold) { normalized.bold = true } return normalized } const normalizeFieldSection = (section) => { const normalized = { type: 'field', key: String(section.key || ''), label: String(section.label || ''), show: section.show !== false, } if (section.suffix) { normalized.suffix = String(section.suffix) } return normalized } const normalizeSection = (section) => { if (!section || !section.type) return null if (section.type === 'line') { return { type: 'line' } } if (section.type === 'text') { return normalizeTextSection(section) } if (section.type === 'field') { return normalizeFieldSection(section) } return null } export const normalizePrintTemplate = (template) => { const sections = Array.isArray(template?.sections) ? template.sections : [] return { cutPaper: template?.cutPaper !== false, sections: sections.map(normalizeSection).filter(Boolean), } } export const parsePrintTemplate = (raw) => { console.log('[print-template] parsePrintTemplate 输入:', raw) if (raw === undefined || raw === null) { console.log('[print-template] 输入为空') return normalizePrintTemplate({ sections: [], cutPaper: true }) } if (typeof raw === 'string') { const text = raw.trim() if (!text) { return normalizePrintTemplate({ sections: [], cutPaper: true }) } try { return parsePrintTemplate(JSON.parse(text)) } catch (error) { console.log('[print-template] JSON 字符串解析失败:', error, text) return normalizePrintTemplate({ sections: [], cutPaper: true }) } } if (Array.isArray(raw.sections)) { const result = normalizePrintTemplate(raw) console.log('[print-template] 直接解析 sections,数量:', result.sections.length) return result } if (typeof raw.msg === 'string' && raw.msg.trim()) { try { const parsed = JSON.parse(raw.msg) if (parsed && Array.isArray(parsed.sections)) { const result = normalizePrintTemplate(parsed) console.log('[print-template] 从 msg 解析,sections 数量:', result.sections.length) return result } } catch (error) { console.log('[print-template] msg JSON 解析失败:', error, raw.msg) } } if (raw.data !== undefined && raw.data !== null) { console.log('[print-template] 尝试解析 data 字段:', raw.data) return parsePrintTemplate(raw.data) } console.log('[print-template] 未能识别模板结构,可用字段:', Object.keys(raw)) return normalizePrintTemplate({ sections: [], cutPaper: true }) } // 保存/获取统一格式:{ cutPaper, sections } export const buildPrintTemplateSaveBody = (template) => { return normalizePrintTemplate(template) } // 极小模板,用于排查 sections 过大是否导致签名/网关异常 export const getMinimalPrintTemplateForTest = () => normalizePrintTemplate({ cutPaper: true, sections: [ { type: 'text', content: '测试', fontSize: 1, align: 'center' }, ], }) export const PRINT_SAMPLE_DATA = { title: '国家税务总局', ticketNo: 'A001', businessType: '综合业务', waitingCount: 5, windowNumber: '3号窗口', } export const getDefaultPrintTemplate = () => normalizePrintTemplate({ cutPaper: true, sections: [ { type: 'text', content: '{{title}}', fontSize: 2, bold: true, align: 'center' }, { type: 'line' }, { type: 'text', content: '{{ticketNo}}', fontSize: 3, bold: true, align: 'center' }, { type: 'line' }, { type: 'field', key: 'businessType', label: '业务', show: true }, { type: 'field', key: 'waitingCount', label: '等候', show: true, suffix: ' 人' }, { type: 'field', key: 'windowNumber', label: '地点', show: true }, { type: 'line' }, { type: 'text', content: '请留意广播和显示屏叫号', fontSize: 1, align: 'center' }, ], }) export const buildPrintPreviewData = (template, context = {}) => { return { title: context.title ?? '', ticketNo: context.ticketNo ?? '——', businessType: context.businessType ?? context.businessName ?? '-', waitingCount: context.waitingCount ?? 0, windowNumber: context.windowNumber ?? '-', rankUserName: context.rankUserName ?? '', ...context, } } export const renderPrintTemplateHtml = (template, data = {}) => { if (!template || !Array.isArray(template.sections)) { return '

暂无打印模板

' } const parts = [ '
', ] template.sections.forEach((section) => { if (section?.show === false) return if (section.type === 'line') { parts.push('
') return } if (section.type === 'text') { const text = replacePlaceholders(section.content, data) const fontSize = FONT_SIZE_MAP[section.fontSize] || FONT_SIZE_MAP[1] const fontWeight = section.bold ? 'bold' : 'normal' const textAlign = section.align || 'left' parts.push( `

${escapeHtml(text)}

` ) return } if (section.type === 'field') { const value = data[section.key] const display = value !== undefined && value !== null && value !== '' ? String(value) : '-' const suffix = section.suffix || '' const label = section.label || '' parts.push( `

${escapeHtml(label)}:${escapeHtml(display)}${escapeHtml(suffix)}

` ) } }) parts.push('
') return parts.join('') }