x-editor-u 富文本编辑(单独付费)
多端富文本编辑容器。接口对齐微信 editor / EditorContext,内容走自有受限 HTML(不跟微信 Delta 对齐)。
兼容性
| Harmony | IOS | Android | WEB | 小程序 |
|---|---|---|---|---|
| 支持 | 支持 | 支持 | 支持 | 支持 |
App:系统编辑控件。Web:宿主 DIV 挂 Quill。微信:官方 editor。
调用
<x-editor-u
ref="editorRef"
placeholder="请输入"
:height="280"
@ready="onReady"
@input="onInput"
@statuschange="onStatus"
@linkchange="onLink"
></x-editor-u>const proxy = getCurrentInstance()!.proxy!
function getEditor() : XEditorUComponentPublicInstance | null {
return proxy.$refs["editorRef"] as XEditorUComponentPublicInstance | null
}
const editor = getEditor()
if (editor == null) return
editor.format("bold", "true")
const htmlOpts : XEditorSetContentsOptions = { html: "<p>hello</p>" }
editor.setContents(htmlOpts)
const readOpts : XEditorGetContentsOptions = {
success: (res : XEditorContents) : void => {
console.log(res.html, res.text)
}
} as XEditorGetContentsOptions
editor.getContents(readOpts)参数
对齐微信 <editor> 属性。
| 字段 | 说明 | 默认 |
|---|---|---|
| placeholder | 占位文字 | 请输入 |
| readOnly | 只读 | false |
| dark | 深色 | false |
| height | 整体高度 px(含内置工具栏) | 350 |
| html | 初始 / 受控 HTML | "" |
| showToolbar | 是否显示内置工具栏 | true |
| showLinkToolbar | 光标在链接上时显示编辑条。微信小程序无 insertLink,工具栏链接入口仅该端隐藏 | true |
| showImgSize | 点选图片显示宽高 | true |
| showImgToolbar | 点选图片显示删除 | true |
| showImgResize | 点选图片拖角缩放(鸿蒙为放大/缩小按钮)。App 端点选图片自动收键盘方便拖手柄,已选中的图片再点一下取消选中 | true |
| enterkeyhint | 键盘回车键文案 | enter |
| confirmHold | 回车是否保持键盘 | true |
事件
对齐微信 bindready / bindfocus / bindblur / bindinput / bindstatuschange。
| 名称 | 说明 |
|---|---|
| ready | 内核就绪 |
| focus / blur | { html, text } |
| input | { html, text } |
| statuschange | 当前光标/选区样式,供工具栏高亮。含 bold italic underline strike link linkText linkIndex linkLength 等 |
| linkchange | 进入或离开链接时触发,{ href, text, index, length },方便自绘工具栏填链接 |
| mentionclick | 点击提及芯片时触发,{ id, name }。微信端提及为纯文本,不触发 |
| tableclick | 点击表格时触发,{ id, rows, cols, cells }。目前仅 Web 端触发(App 端表格为行内编辑,点击即进入单元格);微信端不支持表格,不触发 |
| chooseimage | 内置工具栏点插图。宿主选图、上传后调用 insertImage({ src }) |
| error | 失败 |
方法
对齐微信 EditorContext:
format setContents getContents insertImage insertText insertDivider insertLink updateLink removeLink insertMention insertTable updateTable removeTable undo redo clear removeFormat blur scrollIntoView getSelectionText getHistoryState getSelection setSelection deleteText getBounds
另提供同步读取:getHtml getText。
不实现微信 insertCustomBlock(自定义区块仅官方内核支持)。delta 不作为跨端数据。微信端 getSelection / setSelection / deleteText / getBounds 受官方类型限制,能力以各端内核为准。
format
名对齐微信 EditorContext.format:
bold italic underline strike ins script color backgroundColor fontSize fontWeight fontStyle align direction header list indent textIndent letterSpacing lineHeight textDecoration margin marginTop marginBottom marginLeft marginRight padding paddingTop paddingBottom paddingLeft paddingRight
另支持历史别名 background(等于 backgroundColor)和 link。kebab-case 写法(如 margin-left)会自动归一。
margin / padding 为段落级样式,再次应用相同值会取消。各端能力:
| 名称 | 微信 | Web | iOS | Android | 鸿蒙 |
|---|---|---|---|---|---|
| marginLeft / paddingLeft | 原生 | 支持 | 支持 | 支持(缩进实现) | 支持(缩进实现) |
| marginTop / paddingTop | 原生 | 支持 | 支持(段前距) | 不支持 | 不支持 |
| marginBottom / paddingBottom | 原生 | 支持 | 支持(段后距) | 不支持 | 不支持 |
| margin / padding(四向简写) | 原生 | 支持 | 不支持 | 不支持 | 不支持 |
list 取值:ordered / bullet / check(待办)。indent 取值:-1 / +1。script 取值:sub / super。再点同一样式会取消(与微信一致)。微信官方只有 check 一种待办,且基础库 3.9.3+ 常画成黑点(与无序相同),小程序工具栏因此只保留有序/无序。
列表按整行加前缀,回车自动续写(有序 1. → 2.)。空列表项再回车退出列表。
insertMention
插入提及芯片 @名字,整体删除(退格从中间删会整块删掉),点击触发 mentionclick。
const opts : XEditorInsertMentionOptions = {
name: "张三",
id: "1001",
color: "#1677ff", // 可选,文字色
background: "#e6f3ff", // 可选,背景色
radius: "4px" // 可选,圆角;Android/iOS 绘制为方角,仅参与 HTML 回写
}
editor.insertMention(opts)HTML 回写为 <span data-mention-id="1001" data-mention-name="张三" style="color:...;background-color:...;border-radius:...">@张三</span>,setContents 灌入同构 HTML 会还原成芯片。微信端降级为纯文本 @名字(官方内核无自定义节点),无样式、无点击事件。
表格(insertTable / updateTable / removeTable)
类 iOS 备忘录的表格交互:
- 工具栏表格按钮直接插入 2x2 空表,不弹输入框。App 端插入后不自动聚焦格子、不弹键盘,用户自行点格子开始输入。
- 表格在正文里行内直接编辑(全端一致,无弹层):点格子即打字。
- 光标在某格时,该列顶部、该行左侧出现
•••手柄:点一下选中整列/整行(高亮)并收起键盘,选中态再点弹出菜单——删除列/右侧加一列、删除行/下方加一行。 - 光标在最后一格再按回车,自动在下方续一行。
const opts : XEditorInsertTableOptions = {
rows: 2,
cols: 2,
cells: [["姓名", "分工"], ["张三", "前端"]] // 可选,缺省为空格子
}
editor.insertTable(opts)
// 之后可用 updateTable({ id, cells }) 改内容,removeTable({ id }) 删除HTML 回写为 <table class="x-table"><tr><td>…</td></tr></table>,setContents 灌入同构 HTML 会还原。上限 20 行 x 8 列,单元格为纯文本。各端形态:
| 端 | 呈现 | 编辑方式 |
|---|---|---|
| Web | Quill 原生表格 | 表格内直接打字 + 行内 ••• 手柄增删行列 + 末格回车加行 |
| Android / iOS | 行内真实网格(块式内核:正文与表格块混排) | 格内直接打字 + ••• 手柄增删行列 + 末格回车加行 |
| 鸿蒙 | 行内真实网格(块式内核:多 RichEditor 文本块 + 表格块混排) | 同上 |
| 微信 | 不支持(官方内核无表格),工具栏隐藏按钮,API 走 fail(错误码 3006) | - |
insertImage 对齐微信:src alt width height nowrap extClass data。默认插图后换行,nowrap: true 不换行。
点选图片后出现尺寸、删除和缩放,对齐微信 show-img-size / show-img-toolbar / show-img-resize。App / Web 自绘浮层;微信走官方控件;鸿蒙因无可靠坐标,用编辑区顶栏放大、缩小、删除。设为 false 可关闭。
内置工具栏默认显示,布局、配色和 Remix 图标对齐 tmx-ui x-editor(x-icon / x-popover)。字号、颜色、标题用同样的气泡菜单。多出来的缩进、上下标、分割线、插图、链接、撤销重做、去格式沿用同一组按钮样式。微信小程序没有可用的 insertLink,该端隐藏链接按钮和编辑条;App / Web 正常显示。光标落在已有样式上时对应按钮高亮。点进链接会出现编辑条。点插图会触发 chooseimage,由宿主选图、上传后调用 insertImage({ src }),插件不内置上传。:show-toolbar="false" 后只留编辑面。工具栏用 view 点击,避免抢焦点丢掉选区。需要工程里已有 tmx-ui。
更新日志
0.1.5(2026-08-28)
- 修复 APP 端
native-view拿不到高度导致内容区空白:宿主原本靠flex: 1撑开,而原生层拿不到由 flex 分配出来的尺寸,现改为把props.height直接写成固定像素挂在native-view上。根节点高度已固定,body 是唯一的在流子层,链接条是position: absolute不占高,因此两者相等 - 同一条规则下
flex: 1隐含的flex-basis: 0%会盖掉height,APP 端补一段条件编译把flex-grow/flex-shrink/flex-basis一并退回,确保写上去的像素值生效。本仓其余 native-view 插件(swiper / 天地图 / 相机 / pdf / 人脸 / skia / html / model3d)用的都是固定像素或100%,只有这里是 flex
0.1.4(2026-08-27)
reflowAfterResize里的强制排版从containerRef?.layoutIfNeeded()收窄为scrollHost?.layoutIfNeeded()。对bindIOSView绑定的宿主调layoutIfNeeded会把 uni-app x 的页面布局拽进一次计划外的同步排版,与它自己驱动的 frame 布局撞车。宿主直接子层的 frame 已在layoutHostChildren中同步摆好,这里只需让滚动容器内部的宽度约束落地
0.1.3(2026-08-27)
- 修复 iOS 反复进出后整页空白(导航栏还在、内容区永久不恢复):宿主的直接子层(滚动容器、占位 label、图片手柄层、底部静态工具条)全部改回手排 frame,由
XEditorHostView.layoutSubviews每趟按当前bounds重排。bindIOSView绑定的视图由 uni-app x 的布局引擎按 frame 摆放,在它身上激活 Auto Layout 约束会把宿主拽进约束引擎、与外层 frame 布局打架,整页内容因此塌掉。0.1.1 把这几处从 frame 改成约束正是这个问题的来源,症状也从「重进就好」恶化成「永不恢复」 - 更深层自成一体的子树(滚动容器内的
UIStackView、工具条内部排布)不碰宿主,保持约束布局不变 - 宿主补
clipsToBounds
0.1.2(2026-08-27)
- 修复 iOS 偶发整块空白的主因:文本视图改为在创建时就装配 TextKit 1 排版栈(
UITextView(frame:textContainer:)+ 显式NSTextStorage/NSLayoutManager/NSTextContainer)。本实现整体建立在textStorage/layoutManager/boundingRect(forGlyphRange:in:)之上,而 iOS 16+ 的UITextView默认起 TextKit 2,首次访问这些属性会触发运行时降级(原生日志:UITextView ... is switching to TextKit 1 compatibility mode because its layoutManager was accessed)。降级会把已建好的排版栈整个换掉,落在已上屏、正在布局的视图上就是一块空白;降级时机取决于哪条代码路径先访问,所以表现为偶发
0.1.1(2026-08-27)
- 修复 iOS 反复进出页面后整块空白:
XEditorHostView.layoutSubviews在onLayout尚未绑定时也会把尺寸记进lastSize。attach是在bindIOSView之后才挂回调的,中间只要跑过一次布局,这次尺寸就被吞掉,而之后尺寸不再变化就永远不会再回调,正文块一直停在零宽排出来的版上且不可恢复。现在回调未绑定时不记lastSize,同时把onLayout提到attach最前面绑定 - 补齐
attach时宿主已有尺寸的分支:原来只发ready不重排,现在走完整的reflowAfterResize - 修复销毁不彻底:
destroy()原本只清了 9 个回调里的 6 个,漏掉的onMentionCallback/onTableCallback/onToolbarCallback会留下XEditorU → XEditorNative → 闭包 → XEditorU的循环引用,UTSiOS.destroyInstance只摘桥接表、断不了这一环 destroy()主动收键盘,避免带焦点离开页面时正文块仍是 first responder;textViewDidEndEditing增加 destroyed 守卫,不再向已卸载的 uvue 抛 blurdestroy()清空_options(内含 keepAlive 住的 uvue 回调)
