基于 Tiptap + React 19 的文书编辑器组件库,提供富文本编辑、变量插入、文档预览等能力,同时支持原生 Web Component 方式在非 React 项目中使用。
- 🚀 开箱即用 — 提供完整的 React 组件和原生 Web Component
- ✍️ 富文本编辑 — 基于 Tiptap,支持标题、加粗、斜体、下划线、删除线、列表、对齐、缩进、字体大小、文字颜色、高亮等
- 📊 表格支持 — 插入表格、行列增删、合并单元格
- 🔢 变量系统 — 在文档中插入变量节点,支持多种数据类型(文本、数字、日期、时间、布尔、下拉选择)
- 🖨️ 打印预览 — 内置打印和 HTML 导出功能
- 🎨 自定义扩展 — 分页符、签名节点、首行缩进等自定义节点
- 🌐 跨框架使用 — 通过 Web Component 在 Vue、Angular、原生 HTML 等非 React 项目中使用
bun add document-editor
# 或
npm install document-editorReact 组件模式:宿主项目需自行安装
react^19.2 和react-dom^19.2。Web Component 模式:本包自带完整的react 19运行时。
编辑器是核心组件,提供完整的富文本编辑体验,包含工具栏、变量抽屉、签名支持和引导教程。
import { DocumentEditor, EditorRef } from 'document-editor'
import 'document-editor/document-editor.css'
function App() {
const editorRef = useRef<EditorRef>(null)
return (
<DocumentEditor
ref={editorRef}
placeholder="开始输入..."
content={initialContent}
inputable
onSave={({ editor }) => {
console.log('保存内容:', editor.getJSON())
}}
onUpdate={({ editor }) => {
console.log('内容更新:', editor.getJSON())
}}
variableListProps={{
templateList: [], // 模板列表数据
templateListLoading: false,
variableList: [], // 变量列表数据
variableListLoading: false,
onTemplateSelect: (id) => {
// 选中模板后的回调
},
}}
getEnumsQuery={(params) => {
// 返回枚举列表,用于下拉选择型变量
return [{ value: '1', label: '选项1' }]
}}
// 签名相关
doctorSginImage="data:image/png;base64,..."
patientSginImage="data:image/png;base64,..."
onDoctorSgin={() => console.log('医生签名')}
onPatientSgin={() => console.log('患者签名')}
/>
)
}预览器用于只读模式展示文档内容,支持变量表单输入和打印导出。
import { DocumentPreviewer, PreviewerRef } from 'document-editor'
function Preview() {
const previewRef = useRef<PreviewerRef>(null)
return (
<DocumentPreviewer
ref={previewRef}
content={docContent} // JSONContent 格式的文档内容
formData={{ field1: '值' }} // 预填充的表单数据
inputable // 是否允许在预览中填写变量
getEnumsQuery={(params) => []}
/>
)
}变量列表组件可独立使用,展示模板和变量树,支持搜索和点击插入/替换变量。
import { VariableList } from 'document-editor'
<VariableList
templateList={templates}
templateListLoading={loading}
templateValue={selectedId}
variableList={variables}
variableListLoading={false}
onTemplateSelect={(medicalId) => fetchVariables(medicalId)}
mode="insert" // 'insert' | 'replace'
/>当项目不使用 React 19 时(如 Vue、Angular、Svelte 或原生 HTML),可以通过 Web Component 方式使用文档预览器。
// 导入即自动注册 <document-previewer> 自定义元素
import 'document-editor/wc'
import 'document-editor/wc/document-editor.css'import type { HTMLPreviewerElement } from 'document-editor/wc'
function App() {
const ref = useRef<HTMLPreviewerElement>(null)
return (
<div>
<document-previewer
ref={ref}
content={initialContent}
inputable
/>
<button onClick={() => console.log(ref.current?.getPreviewHTML?.())}>
输出 HTML
</button>
<button onClick={() => ref.current?.print?.()}>
打印
</button>
</div>
)
}| 属性/方法 | 类型 | 说明 |
|---|---|---|
editor |
Editor |
Tiptap Editor 实例,可直接调用所有 Tiptap API |
print() |
() => Promise<void> |
触发打印 |
form |
FormInstance |
antd Form 实例,用于获取变量表单值 |
getPreviewHTML() |
() => string |
获取预览用 HTML 字符串 |
| 属性/方法 | 类型 | 说明 |
|---|---|---|
editor |
Editor |
Tiptap Editor 实例 |
print() |
() => void |
触发打印 |
form |
FormInstance |
antd Form 实例 |
getPreviewHTML() |
() => string |
获取预览用 HTML 字符串 |
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
placeholder |
string |
'开始输入...' |
编辑器占位文本 |
content |
JSONContent |
— | 编辑器初始内容(Tiptap JSON 格式) |
inputable |
boolean |
— | 是否允许在编辑器中输入变量值 |
onSave |
({ editor }) => void |
— | 保存回调 |
onUpdate |
({ editor }) => void |
— | 内容更新回调 |
onFocus |
({ editor }) => void |
— | 聚焦回调 |
onBlur |
({ editor }) => void |
— | 失焦回调 |
variableListProps |
Omit<VariableListProps, 'mode'> |
必填 | 变量列表配置 |
getEnumsQuery |
(params) => EnumsItem[] |
— | 枚举查询函数,用于下拉变量 |
doctorSginImage |
string |
— | 医生签名图片(Base64) |
patientSginImage |
string |
— | 患者签名图片(Base64) |
familySginImage |
string |
— | 家属签名图片(Base64) |
onDoctorSgin |
() => void |
— | 医生签名点击回调 |
onPatientSgin |
() => void |
— | 患者签名点击回调 |
onFamilySgin |
() => void |
— | 家属签名点击回调 |
| 属性 | 类型 | 说明 |
|---|---|---|
content |
JSONContent |
预览的文档内容 |
formData |
any |
预填充的表单数据 |
inputable |
boolean |
是否允许在预览中填写变量 |
getEnumsQuery |
(params) => EnumsItem[] |
枚举查询函数 |
| 属性 | 类型 | 说明 |
|---|---|---|
templateList |
Array |
模板列表数据 |
templateListLoading |
boolean |
模板列表加载状态 |
templateValue |
string |
当前选中的模板 ID |
variableList |
InformedTemplateParagraphListItem[] |
变量/段落列表 |
variableListLoading |
boolean |
变量列表加载状态 |
onTemplateSelect |
(medicalId: string) => void |
模板选中回调 |
编辑器支持在文档中插入变量节点,变量分为以下类型:
| 类型 | 说明 | 渲染形式 |
|---|---|---|
text |
文本输入 | <Input /> |
number |
数字输入 | <Input type="number" /> |
date |
日期选择 | <DatePicker /> |
time |
时间选择 | <TimePicker /> |
date-time |
日期时间选择 | <DatePicker showTime /> |
boolean |
布尔选择 | 单选按钮组 |
select |
下拉选择(有值域) | <Select />(通过 getEnumsQuery 获取选项) |
- 点击工具栏 「插入变量」 按钮打开变量抽屉
- 选择模板 → 展开节点 → 点击变量元素即可插入
- 支持搜索过滤变量
- 选中已有的变量节点
- 在弹出的气泡菜单中点击 「替换变量」
- 在抽屉中选择新变量即可替换
选中变量后,可在气泡菜单中点击 「设置别名」 为该变量设置显示别名,别名会替代原始标签显示。
变量节点默认显示字段名标签,可通过气泡菜单中的 「显示字段名」 按钮切换。
工具栏提供三种签名类型按钮:医生签名、患者签名、家属签名。点击即可在光标处插入签名占位符。签名节点也支持别名设置和字段名显示切换。
点击工具栏 「分页符」 按钮可在光标处插入分页符,打印时会在分页符处换页。
- 插入表格:点击工具栏表格按钮,拖拽选择行列数(1×1 ~ 8×8)
- 选中表格后,气泡菜单提供:上方/下方添加行、左侧/右侧添加列、删除行/列、删除表格
- 支持合并单元格(通过 Tiptap 原生表格命令)
工具栏提供导入和导出按钮,支持将编辑器内容导出为 JSON 文件,或从 JSON 文件导入内容。
| 快捷键 | 功能 |
|---|---|
Ctrl+B |
加粗 |
Ctrl+I |
斜体 |
Ctrl+U |
下划线 |
Ctrl+Z |
撤销 |
Ctrl+Shift+Z / Ctrl+Y |
重做 |
const ref = useRef<EditorRef>(null)
// 获取 JSON 格式内容(用于存储)
const json = ref.current?.editor.getJSON()
// 获取 HTML 格式内容
const html = ref.current?.editor.getHTML()
// 获取预览用 HTML(含样式)
const previewHtml = ref.current?.getPreviewHTML()
// 获取表单数据(变量值)
const formValues = ref.current?.form.getFieldsValue()
// 打印
await ref.current?.print()如果你的项目使用 Vue、Angular、Svelte、jQuery 或原生 HTML,无法直接使用 React 组件,可以通过 Web Component 方式集成。
项目使用 @r2wc/react-to-web-component 将 React 组件包装为标准的 Custom Element。<document-previewer> 是一个注册在全局的自定义元素,可以在任何框架中使用。
参考 examples/wc-demo/ 目录,其中包含一个完整的使用示例。
Web Component 的属性通过 HTML attribute 或 JavaScript property 传递:
const el = document.querySelector('document-previewer')
// JSON 类型属性
el.content = { type: 'doc', content: [...] }
el.formData = { field1: 'value' }
// 布尔类型属性
el.inputable = true
// 函数类型属性
el.getEnumsQuery = (params) => [{ value: '1', label: '选项' }]
// 字符串类型属性
el.className = 'my-previewer'const el = document.querySelector('document-previewer')
// 获取预览 HTML
const html = el.getPreviewHTML()
// 打印
el.print()
// 获取表单值
const values = el.form.getFieldsValue()
// 访问 Tiptap Editor 实例
const json = el.editor.getJSON()- 样式隔离:Web Component 使用 Light DOM 模式,样式需要手动引入 CSS 文件
- React 依赖:即使使用 Web Component,宿主项目仍需安装
react和react-dom作为 peer dependency - JSON 属性:
content和formData为 JSON 类型,需通过 JS property 设置,不能通过 HTML attribute 传递
# 如果使用 mise 则执行这一步
mise install
# 如果不使用 mise 则执行这两步
nvm use 24
npm install bun@1 -g
bun install
bun devhttp://localhost:4004/editor?account=900101&tenant_id=900101&password=240408
# 构建 React 组件库
bun run build
# 构建 Web Component
bun run build:wc
# 构建 Demo
bun run build:demo# 单次生成
bun run codegen
# 监听模式(文件变更自动重新生成)
bun run codegen:watchAPI 代码生成配置位于 scripts/codegen-api/,基于 OpenAPI 规范文件 openapi/openapi.json 自动生成 TypeScript 类型和请求函数。
src/
├── components/
│ ├── DocumentEditor/ # 编辑器组件
│ │ ├── contexts/ # Context(编辑器实例、枚举、签名事件)
│ │ ├── extensions/ # Tiptap 自定义扩展
│ │ │ ├── VariableExtension.ts # 变量抽屉状态管理
│ │ │ ├── VariableNode/ # 变量节点
│ │ │ ├── SginNode/ # 签名节点
│ │ │ ├── PageBreakNode.ts # 分页符节点
│ │ │ ├── TextIndentNode.ts # 首行缩进
│ │ │ └── TableExtension.ts # 表格扩展
│ │ ├── Toolbar/ # 工具栏
│ │ └── EditorTour/ # 新手引导
│ ├── DocumentPreviewer/ # 预览器组件
│ ├── VariableList/ # 变量列表组件
│ └── PreviewField/ # 预览字段组件
├── wc.ts # Web Component 入口
├── index.ts # React 组件入口
└── utils/ # 工具函数(打印、HTML 导出)
! 禁止修改generated目录下的文件,这些文件由插件生成,任何修改都会被覆盖。