跳转到内容

富文本编辑器 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。它完整保留段落、格式、组件节点和自定义节点的数据。

编辑器不内置固定工具栏。可以使用组件库的 ButtonSpace 组合产品所需的工具栏,并调用 formatText()undo()redo()insertText() 等实例方法。

insertComponent() 内置识别 JsonForm 的全部节点以及 tag,并渲染真正的 SD Design 组件。组件节点位于 Lexical 文档结构中,可以参与选区、整体删除、历史记录和 JSON 序列化。

RichTextEditor 直接复用 JsonForm 的内置组件注册表,而不是维护另一份组件映射。RICH_TEXT_EDITOR_JSON_FORM_NODE_NAMES 提供当前支持的完整名称列表。

自定义组件使用 node-${name} 动态插槽。插槽内容仍处于当前 Vue 应用上下文,可以继续获取 ConfigProvider、locale、主题和 provide/inject。若 ConfigProvider 的 jsonForm.components 注册了外部节点,RichTextEditor 也会自动使用同一份注册配置;同名动态插槽的优先级最高。

组件提供 HTML、Markdown 和 Lexical JSON 三类转换入口。Lexical JSON 是推荐的持久化格式;HTML 和 Markdown 更适合内容交换。

通过 editorplugins 使用未被高层 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-configpluginshistory 属于初始化配置,挂载后不应动态替换。运行期扩展应通过原始 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>> -
事件名 描述 参数
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 }
方法或属性 描述
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 使用。

分类 name
文本输入 autoCompleteinputinputNumberinputPasswordinputSearchinputTagmentiontextareaverificationCode
选择控件 cascadercheckboxcheckboxGroupradioradioGroupselecttreeSelect
日期时间 datePickerrangePickertimePicker
数值与状态 ratesliderswitch
数据与结构 transferrownoFormItem
RichTextEditor 扩展 tag

这些节点的 props 会透传给对应组件,但必须保持 JSON 可序列化。支持 fitWidth 的组件节点在 RichTextEditor 中默认启用内容宽度,可通过节点的 props.fitWidth: false 单独关闭。row 在富文本中作为单个组件节点渲染;noFormItem 默认显示 textValue,也可以用 node-noFormItem 插槽覆盖。

RichTextEditorSemanticType 包含 rootcontentplaceholdercomponent

组件内部使用同版本的 Lexical 官方包。若业务代码需要直接从 lexical@lexical/* 导入命令、节点或插件,应在业务项目中显式安装对应包,并确保版本与 @sdata/web-vue 使用的 Lexical 版本一致。