# **基于 Novel (Tiptap) 的 CRF 表单扩展开发指南** **目标**: 在编辑器中实现 "填空"、"单选"、"日期选择" 等 CRF 控件,并支持导出 Word。 ## **1\. 自定义 CRF 节点开发 (Tiptap Extensions)** 我们需要开发一组 **Node Extensions**,让编辑器理解表单元素。 ### **1.1 填空输入框 (UnderlineInput)** // extensions/underline-input.tsx import { Node, mergeAttributes } from '@tiptap/core'; import { ReactNodeViewRenderer, NodeViewWrapper } from '@tiptap/react'; export const UnderlineInput \= Node.create({ name: 'underlineInput', group: 'inline', inline: true, atom: true, // 作为一个整体,不可分割 addAttributes() { return { placeholder: { default: '请输入...' }, value: { default: '' }, } }, parseHTML() { return \[{ tag: 'span\[data-type="input"\]' }\] }, renderHTML({ HTMLAttributes }) { return \['span', mergeAttributes(HTMLAttributes, { 'data-type': 'input' })\] }, // React 组件渲染 addNodeView() { return ReactNodeViewRenderer(({ node, updateAttributes }) \=\> { return ( \ \ updateAttributes({ value: e.target.value })} /\> \ ) }) }, }); ### **1.2 单选组 (RadioGroup)** // extensions/radio-group.tsx // 类似逻辑,渲染一组 radio buttons ## **2\. AI 生成与解析策略** 如何让 AI 生成这些控件?我们约定一套 **"占位符语法"**。 * **Prompt**: "生成一个性别选择项,包含男、女。" * **AI Output**: 性别: {{radio:男,女}} * **前端解析 (Replacement Logic)**: // 在 useAIStream 的 onStream 更新中 const parseContent \= (text) \=\> { // 正则替换 if (text.includes('{{input}}')) { editor.chain().focus().insertContent({ type: 'underlineInput' }).run(); } // ... }; ## **3\. Word 导出逻辑 (docx.js)** 针对自定义节点的导出映射。 // utils/export-docx.ts import { TextRun, UnderlineType } from "docx"; export const transformNode \= (node) \=\> { switch (node.type) { // 导出填空框 \-\> 带下划线的空格 case 'underlineInput': return new TextRun({ text: node.attrs.value || " ", // 有值填值,无值填空格 underline: { type: UnderlineType.SINGLE, }, }); // 导出复选框 \-\> 特殊字符 case 'taskItem': const isChecked \= node.attrs.checked; return new TextRun({ text: isChecked ? "☑ " : "☐ ", // Unicode 字符 font: "Arial Unicode MS", // 确保字体支持 }); // ... 其他节点 } }; ## **4\. 总结** Novel (Tiptap) 完全有能力承载 CRF 的需求。 虽然这需要一些 **"Extension 开发"** 的工作量,但相比自己从头写一个 Form Builder,这是性价比最高的方案,而且还能保持文档的流式阅读体验。