/** * A4 画布正文解析(配合 pages/hr/components/A4Preview.vue 使用)。 * * 后端 documentContent 的字段口径:「A4画布正文JSON或HTML;变量统一使用双花括号编码占位, * 例如 {{EMPLOYEE_NAME}}」。这里统一解析成三种形态: * - blocks:JSON 画布({ blocks: [...] } 或 block 数组),拆成可渲染的块 * - html :HTML 正文,交给 rich-text 渲染 * - text :无法解析时按纯文本展示,保证历史数据也能看到内容 */ const TITLE_TYPES = ["title", "heading", "h1", "h2", "header"]; const SIGN_TYPES = ["sign", "signature", "date", "footer"]; /** 把一段文本按 {{变量名}} 切成普通文本 / 变量片段,供预览高亮 */ export function splitVariableSegments(value) { const source = String(value == null ? "" : value); const pattern = /\{\{\s*([^{}]+?)\s*\}\}/g; const segments = []; let last = 0; let match = pattern.exec(source); while (match) { if (match.index > last) { segments.push({ text: source.slice(last, match.index), isVar: false }); } segments.push({ text: match[1], isVar: true }); last = match.index + match[0].length; match = pattern.exec(source); } if (last < source.length) segments.push({ text: source.slice(last), isVar: false }); return segments; } function cellText(cell) { if (cell && typeof cell === "object") { return String(cell.text != null ? cell.text : cell.value != null ? cell.value : ""); } return cell == null ? "" : String(cell); } function normalizeRows(block) { const rows = block.rows || block.cells || block.table; if (!Array.isArray(rows)) return []; const result = []; rows.forEach((rawRow) => { const rowArray = Array.isArray(rawRow) ? rawRow : [rawRow]; // 每行的每个 cell 拆成 VariableSegment[];空行丢弃 const cells = rowArray.map((cell) => splitVariableSegments(cellText(cell))); if (cells.length) result.push(cells); }); return result; } function normalizeBlocks(parsed) { const raw = Array.isArray(parsed) ? parsed : parsed && parsed.blocks; if (!Array.isArray(raw)) return []; return raw.reduce((blocks, item, index) => { const key = `block-${index}`; if (item == null) return blocks; if (typeof item !== "object") { const text = String(item); if (text.trim()) blocks.push({ key, kind: "para", segments: splitVariableSegments(text) }); return blocks; } const block = item; const type = String(block.type || "").toLowerCase(); if (type === "table") { const rows = normalizeRows(block); if (rows.length) blocks.push({ key, kind: "table", rows }); return blocks; } const text = block.text != null ? block.text : block.content != null ? block.content : block.value != null ? block.value : ""; if (!String(text).trim()) return blocks; const kind = TITLE_TYPES.indexOf(type) >= 0 ? "title" : SIGN_TYPES.indexOf(type) >= 0 ? "sign" : "para"; blocks.push({ key, kind, segments: splitVariableSegments(text) }); return blocks; }, []); } /** HTML 正文里去掉脚本与内联事件,避免画布内容带出可执行代码 */ function sanitizeHtml(html) { return String(html) .replace(//gi, "") .replace(/]*>/gi, "") .replace(/\son\w+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, ""); } /** 解析画布正文 */ export function parseA4Content(content = "") { const text = String(content == null ? "" : content).trim(); if (!text) return { type: "empty", blocks: [], html: "", text: "" }; if (text.charAt(0) === "<") return { type: "html", blocks: [], html: sanitizeHtml(text), text: "" }; if (text.charAt(0) === "{" || text.charAt(0) === "[") { try { const blocks = normalizeBlocks(JSON.parse(text)); if (blocks.length) return { type: "blocks", blocks, html: "", text: "" }; } catch (error) { // 解析失败按纯文本展示 } } return { type: "text", blocks: [], html: "", text }; } export default { parseA4Content, splitVariableSegments };