- Add one-click research protocol generation with streaming output - Implement Word document export via Pandoc integration - Add dynamic dual-panel layout with resizable split pane - Implement collapsible content for StatePanel stages - Add conversation history management with title auto-update - Fix scroll behavior, markdown rendering, and UI layout issues - Simplify conversation creation logic for reliability
3.3 KiB
基于 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 (
<NodeViewWrapper as="span" className="inline-block mx-1">
<input
className="border-b border-black outline-none px-1 w-24 bg-transparent text-center focus:border-blue-500"
placeholder={node.attrs.placeholder}
value={node.attrs.value}
onChange={(e) => updateAttributes({ value: e.target.value })}
/>
</NodeViewWrapper>
)
})
},
});
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,这是性价比最高的方案,而且还能保持文档的流式阅读体验。