a4-preview.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. /**
  2. * A4 画布正文解析(配合 pages/hr/components/A4Preview.vue 使用)。
  3. *
  4. * 后端 documentContent 的字段口径:「A4画布正文JSON或HTML;变量统一使用双花括号编码占位,
  5. * 例如 {{EMPLOYEE_NAME}}」。这里统一解析成三种形态:
  6. * - blocks:JSON 画布({ blocks: [...] } 或 block 数组),拆成可渲染的块
  7. * - html :HTML 正文,交给 rich-text 渲染
  8. * - text :无法解析时按纯文本展示,保证历史数据也能看到内容
  9. */
  10. const TITLE_TYPES = ["title", "heading", "h1", "h2", "header"];
  11. const SIGN_TYPES = ["sign", "signature", "date", "footer"];
  12. /** 把一段文本按 {{变量名}} 切成普通文本 / 变量片段,供预览高亮 */
  13. export function splitVariableSegments(value) {
  14. const source = String(value == null ? "" : value);
  15. const pattern = /\{\{\s*([^{}]+?)\s*\}\}/g;
  16. const segments = [];
  17. let last = 0;
  18. let match = pattern.exec(source);
  19. while (match) {
  20. if (match.index > last) {
  21. segments.push({ text: source.slice(last, match.index), isVar: false });
  22. }
  23. segments.push({ text: match[1], isVar: true });
  24. last = match.index + match[0].length;
  25. match = pattern.exec(source);
  26. }
  27. if (last < source.length) segments.push({ text: source.slice(last), isVar: false });
  28. return segments;
  29. }
  30. function cellText(cell) {
  31. if (cell && typeof cell === "object") {
  32. return String(cell.text != null ? cell.text : cell.value != null ? cell.value : "");
  33. }
  34. return cell == null ? "" : String(cell);
  35. }
  36. function normalizeRows(block) {
  37. const rows = block.rows || block.cells || block.table;
  38. if (!Array.isArray(rows)) return [];
  39. const result = [];
  40. rows.forEach((rawRow) => {
  41. const rowArray = Array.isArray(rawRow) ? rawRow : [rawRow];
  42. // 每行的每个 cell 拆成 VariableSegment[];空行丢弃
  43. const cells = rowArray.map((cell) => splitVariableSegments(cellText(cell)));
  44. if (cells.length) result.push(cells);
  45. });
  46. return result;
  47. }
  48. function normalizeBlocks(parsed) {
  49. const raw = Array.isArray(parsed) ? parsed : parsed && parsed.blocks;
  50. if (!Array.isArray(raw)) return [];
  51. return raw.reduce((blocks, item, index) => {
  52. const key = `block-${index}`;
  53. if (item == null) return blocks;
  54. if (typeof item !== "object") {
  55. const text = String(item);
  56. if (text.trim()) blocks.push({ key, kind: "para", segments: splitVariableSegments(text) });
  57. return blocks;
  58. }
  59. const block = item;
  60. const type = String(block.type || "").toLowerCase();
  61. if (type === "table") {
  62. const rows = normalizeRows(block);
  63. if (rows.length) blocks.push({ key, kind: "table", rows });
  64. return blocks;
  65. }
  66. const text = block.text != null ? block.text : block.content != null ? block.content : block.value != null ? block.value : "";
  67. if (!String(text).trim()) return blocks;
  68. const kind = TITLE_TYPES.indexOf(type) >= 0 ? "title" : SIGN_TYPES.indexOf(type) >= 0 ? "sign" : "para";
  69. blocks.push({ key, kind, segments: splitVariableSegments(text) });
  70. return blocks;
  71. }, []);
  72. }
  73. /** HTML 正文里去掉脚本与内联事件,避免画布内容带出可执行代码 */
  74. function sanitizeHtml(html) {
  75. return String(html)
  76. .replace(/<script[\s\S]*?<\/script>/gi, "")
  77. .replace(/<script[^>]*>/gi, "")
  78. .replace(/\son\w+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, "");
  79. }
  80. /** 解析画布正文 */
  81. export function parseA4Content(content = "") {
  82. const text = String(content == null ? "" : content).trim();
  83. if (!text) return { type: "empty", blocks: [], html: "", text: "" };
  84. if (text.charAt(0) === "<") return { type: "html", blocks: [], html: sanitizeHtml(text), text: "" };
  85. if (text.charAt(0) === "{" || text.charAt(0) === "[") {
  86. try {
  87. const blocks = normalizeBlocks(JSON.parse(text));
  88. if (blocks.length) return { type: "blocks", blocks, html: "", text: "" };
  89. } catch (error) {
  90. // 解析失败按纯文本展示
  91. }
  92. }
  93. return { type: "text", blocks: [], html: "", text };
  94. }
  95. export default { parseA4Content, splitVariableSegments };