富文本编辑器 RichTextEditor
RichTextEditor 使用 Lexical 管理 EditorState、DOM reconciliation、选区、输入法、撤销与重做。组件库负责 Vue 生命周期、主题样式、JsonForm 全部内置节点以及 Tag 等内联组件节点。
组件不会把 Lexical 隐藏成能力受限的黑盒。实例的 editor 属性始终返回原始 LexicalEditor,可以直接注册命令、listener、node transform、自定义节点和第三方 Lexical 插件;组件同时提供常用的 Vue 友好方法。
- 构建带格式的文本输入、模板编辑、Prompt 编辑或内容创作界面。
- 需要在文本流中插入 Input、Select、Tag 或业务组件。
- 需要保存完整富文本结构,而不是只保存 HTML。
- 需要继续使用 Lexical 原生扩展能力,同时由组件库统一样式和生命周期。
v-model 的值是 Lexical 的序列化 EditorState。它完整保留段落、格式、组件节点和自定义节点的数据。
工具栏与实例方法
Section titled “工具栏与实例方法”编辑器不内置固定工具栏。可以使用组件库的 Button、Space 组合产品所需的工具栏,并调用 formatText()、undo()、redo()、insertText() 等实例方法。
内联组件节点
Section titled “内联组件节点”insertComponent() 内置识别 JsonForm 的全部节点以及 tag,并渲染真正的 SD Design 组件。组件节点位于 Lexical 文档结构中,可以参与选区、整体删除、历史记录和 JSON 序列化。
JsonForm 全部节点
Section titled “JsonForm 全部节点”RichTextEditor 直接复用 JsonForm 的内置组件注册表,而不是维护另一份组件映射。RICH_TEXT_EDITOR_JSON_FORM_NODE_NAMES 提供当前支持的完整名称列表。
自定义 Vue 节点
Section titled “自定义 Vue 节点”自定义组件使用 node-${name} 动态插槽。插槽内容仍处于当前 Vue 应用上下文,可以继续获取 ConfigProvider、locale、主题和 provide/inject。若 ConfigProvider 的 jsonForm.components 注册了外部节点,RichTextEditor 也会自动使用同一份注册配置;同名动态插槽的优先级最高。
HTML 与 Markdown
Section titled “HTML 与 Markdown”组件提供 HTML、Markdown 和 Lexical JSON 三类转换入口。Lexical JSON 是推荐的持久化格式;HTML 和 Markdown 更适合内容交换。
使用原始 Lexical API
Section titled “使用原始 Lexical API”通过 editor 或 plugins 使用未被高层 API 包装的 Lexical 能力。plugins 在组件挂载后执行,并在组件卸载时自动调用返回的清理函数。
RichTextEditorValue 等同于 Lexical 的 SerializedEditorState。受控模式下,组件会比较内部最后一次输出和外部值,避免父组件原样回传时重复替换 EditorState。
需要按顺序组合普通文本和组件节点时,可以把 RichTextEditorContentItem[] 传给 default-value,或使用 getContent()、setContent()、insertContent()。这组 API 由 RichTextEditor 统一处理节点顺序、选区和插入位置,Sender 等复合组件无需再维护一套 contenteditable 实现。注意 getContent/setContent 以单段落为模型:字符串中的 \n 渲染为软换行(LineBreakNode),多个顶层段落往返会塌缩为单段内的软换行;如需保留多段落结构请直接使用 getJSON/setJSON。
组件节点只允许存储 JSON 可序列化数据:
interface RichTextEditorComponentNodeData { key: string; name: string; value?: JsonValue; props?: JsonObject; textValue?: string;}Vue 组件、VNode、回调和不可序列化对象应放在插槽、插件或外部注册表中,不应放进节点数据。
插槽收到的 node 是与 EditorState 隔离的深只读快照(类型为 ReadonlyDeep<RichTextEditorComponentNodeData>)。修改组件节点值必须调用 update(value),删除节点调用 remove();直接改写快照不会改变编辑器状态。
受控模式下,v-model 回传组件库刚输出的同一份值时会被短路,不会重复替换 EditorState。但父组件若主动设置一份新内容,setJSON 会经 parseEditorState 重建 EditorState,所有内联组件节点会重新挂载(内嵌 Input/Select 等会丢失焦点与内部状态)。因此组件节点更适合在文档内编辑,不宜在协作高频推送或频繁整体替换 EditorState 的场景下依赖其内部状态连续性。
常用操作优先使用实例方法;需要完整 Lexical 能力时直接使用 editor:
const editor = editorRef.value?.editor;
const unregister = editor?.registerUpdateListener(({ editorState }) => { editorState.read(() => { // 可以使用任意 Lexical $ helper });});自定义 Lexical Node 必须通过 editor-config.nodes 在编辑器创建前注册:
<RichTextEditor :editor-config="{ namespace: 'MyEditor', nodes: [MyCustomNode], theme: myLexicalTheme, }"/>editor-config、plugins 和 history 属于初始化配置,挂载后不应动态替换。运行期扩展应通过原始 editor 注册,并保存返回的清理函数。
| 参数名 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| v-model | 受控的 Lexical 序列化 EditorState | RichTextEditorValue |
- |
| default-value | 非受控初始状态、纯文本或文本/组件节点序列 | RichTextEditorValue | string | RichTextEditorContentItem[] |
- |
| editor-config | 透传给 createEditor() 的 namespace、nodes、theme、html、onError 等配置 |
RichTextEditorConfig |
- |
| plugins | 编辑器初始化插件;返回值作为卸载清理函数 | RichTextEditorPlugin[] |
[] |
| transformers | Markdown 导入导出转换器 | Transformer[] |
TRANSFORMERS |
| placeholder | 空内容提示 | string |
- |
| disabled | 禁用编辑和内联组件 | boolean |
false |
| readonly | 只读状态 | boolean |
false |
| history | 是否注册 Lexical History | boolean |
true |
| history-delay | 连续输入合并历史的间隔 | number |
300 |
| history-max-depth | 最大撤销深度,null 表示不限制 |
number | null |
null |
| auto-size | 自适应高度及行数约束 | boolean | { minRows?: number; maxRows?: number } |
true |
| spellcheck | 是否启用浏览器拼写检查 | boolean |
true |
| aria-label | 输入区无障碍名称 | string |
富文本编辑器 |
| class-names | 语义化节点类名 | Partial<Record<RichTextEditorSemanticType, string>> |
- |
| styles | 语义化节点样式 | Partial<Record<RichTextEditorSemanticType, CSSProperties>> |
- |
Events
Section titled “Events”| 事件名 | 描述 | 参数 |
|---|---|---|
| update:model-value | 文档内容变化 | value: RichTextEditorValue |
| change | 内容变化;纯选区变化不会触发 | value, { editor, editorState, tags } |
| update | 任意 Lexical update,包括选区变化 | { editor, editorState, tags } |
| ready | LexicalEditor 已创建并绑定根元素 | editor: LexicalEditor |
| focus | 编辑区获得焦点 | event: FocusEvent |
| blur | 编辑区失去焦点 | event: FocusEvent |
| error | 初始化、解析或运行错误 | error: Error |
| 插槽名 | 描述 | 参数 |
|---|---|---|
| placeholder | 自定义空内容提示 | - |
| node-name | 渲染指定名称的自定义组件节点 | { nodeKey, node, disabled, readonly, update, remove, editor } |
Methods
Section titled “Methods”| 方法或属性 | 描述 |
|---|---|
| editor | 原始 LexicalEditor;完整 Lexical 能力入口 |
| editorState | 当前不可变 EditorState |
| rootElement | Lexical 绑定的 contenteditable 根元素 |
| canUndo / canRedo | 当前是否可以撤销或重做 |
| focus() / blur() | 聚焦或取消聚焦 |
| clear() | 清空文档并保留一个空段落 |
| undo() / redo() | 撤销或重做 |
| getJSON() / setJSON() | 读写 Lexical JSON |
| getContent() / setContent() | 按顺序读写文本与组件节点序列 |
| getText() | 获取纯文本;组件节点使用 textValue |
| getHTML() / setHTML() | 导入导出 HTML |
| getMarkdown() / setMarkdown() | 导入导出 Markdown,可指定 transformers |
| insertText() | 在当前选区插入文本 |
| insertContent() | 在开头、结尾或当前选区插入文本/组件节点序列 |
| insertComponent() | 插入内联组件节点 |
| updateComponent() | 按 Lexical NodeKey 更新组件节点值 |
| removeComponent() | 按 Lexical NodeKey 删除组件节点 |
| formatText() | 派发 Lexical FORMAT_TEXT_COMMAND |
| read() / update() | 在合法的 Lexical 读取或更新上下文中执行回调 |
| dispatchCommand() | 派发任意 LexicalCommand |
| registerCommand() | 注册任意 LexicalCommand listener |
| registerUpdateListener() | 注册 Lexical update listener |
| registerMutationListener() | 注册节点 mutation listener |
| registerNodeTransform() | 注册节点 transform |
实例方法只是常用入口,不构成 Lexical 能力白名单。未列出的能力继续通过 editor 使用。
内置组件节点
Section titled “内置组件节点”| 分类 | name |
|---|---|
| 文本输入 | autoComplete、input、inputNumber、inputPassword、inputSearch、inputTag、mention、textarea、verificationCode |
| 选择控件 | cascader、checkbox、checkboxGroup、radio、radioGroup、select、treeSelect |
| 日期时间 | datePicker、rangePicker、timePicker |
| 数值与状态 | rate、slider、switch |
| 数据与结构 | transfer、row、noFormItem |
| RichTextEditor 扩展 | tag |
这些节点的 props 会透传给对应组件,但必须保持 JSON 可序列化。支持 fitWidth 的组件节点在 RichTextEditor 中默认启用内容宽度,可通过节点的 props.fitWidth: false 单独关闭。row 在富文本中作为单个组件节点渲染;noFormItem 默认显示 textValue,也可以用 node-noFormItem 插槽覆盖。
RichTextEditorSemanticType 包含 root、content、placeholder 和 component。
组件内部使用同版本的 Lexical 官方包。若业务代码需要直接从 lexical、@lexical/* 导入命令、节点或插件,应在业务项目中显式安装对应包,并确保版本与 @sdata/web-vue 使用的 Lexical 版本一致。