From 678c2d683441cbbf7d9fa65f363331ddbf451737 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 27 Apr 2026 13:21:27 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(im):=20MessageInput=20?= =?UTF-8?q?=E5=88=87=20contenteditable=20+=20MentionPicker=20=E5=AF=B9?= =?UTF-8?q?=E9=BD=90=E5=BE=AE=E4=BF=A1=EF=BC=8C=E4=BF=AE=E4=B8=80=E5=A0=86?= =?UTF-8?q?=20@=20=E6=B5=AE=E5=B1=82=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【MessageInput.vue】 - textarea → contenteditable div:拿真·光标 rect 给浮层定位(textarea 拿不到),@ 成员 以 token 节点存在,删 token 即删 atUserIds - collectFromEditor:DOM walk 还原 plain text + atUserIds(text / br / span[data-id] / div / 其他元素 五种节点分支),过滤零宽空格 - handleSend:从 DOM 收集而非 ref,atUserIds 走 Set 去重;分步注释 - placeholder 用 [data-empty]::before + JS 维护属性"存在 / 缺失"模拟,避开浏览器删空 后留
让 :empty 不命中 - @ 浮层位置:bottom 锚定(picker 下沿贴 @ 上方 8px),无论候选多寡下沿固定,不再 随 picker 高度变化漂移;上方放不下才翻成 top 锚定到 @ 下方 - @ 浮层规则:regex 改成 `(?:^|\s)@([^\s@]*)$`,避免 email-like "test@example.com" 误触发;锚定在 @ 字符位置而非 caret,否则用户每多敲一字浮层右移 - click outside 关浮层:document mousedown 监听,target 不在 editor / picker 内即关 - Enter 兜底:mention 浮层无候选时 fall through 到正常发送,避免按 Enter 没反应 - token 首位 ZWSP:token 是 editor 第一个节点时 contenteditable=false 边缘会让光标 无法挪到 token 前,补一个零宽空格当锚点;DOM walk 滤掉 - Shift+Enter 强制 br(execCommand insertLineBreak),DOM walk 不必处理多换行容器 - onPaste 用 execCommand('insertText') 剥光所有 HTML,不留外部样式 / 脚本 - onEditorScroll 同步浮层位置,多行 + 滚动条场景下 picker 跟随 caret - selection 保存:document selectionchange 监听 + 仅 editor 内时记录,emoji 面板偷 焦点后能回到原位 【MentionPicker.vue】 - 视觉对齐微信 PC:顶部"所有人"虚拟项(蓝方块 + UserFilled 图标)+ "群成员"分组 header + 底部三角指针;rounded-md + soft shadow - "全体成员" → "所有人";userId=-1 / 文案常量化到 utils/constants.ts (IM_AT_ALL_USER_ID / IM_AT_ALL_NICKNAME),三个文件共用,不再散落 - !fixed + !h-75 / max-height:用 UnoCSS important 变体压过 Element Plus 的 .el-scrollbar { position:relative; height:100% } 默认 CSS——之前 picker 落到父 容器坐标系导致 y=1326 飞出视口外,肉眼看不到的根因 - pos prop 从 {x, y} → {x, top?, bottom?},配合 MessageInput 的 bottom 锚定 - allItem / memberItems 拆成两个 computed,showMembers 做扁平合并供键盘导航; 群成员上限 100 去掉,浮层本就支持滚动 - 5 个内部函数 / watch 全部补 JSDoc(showMembers / visible 两个 watch、scrollToTop / scrollToActive / handleSelect) --- .../components/input/MentionPicker.vue | 134 +++-- .../components/input/MessageInput.vue | 464 +++++++++++++----- 2 files changed, 445 insertions(+), 153 deletions(-) diff --git a/src/views/im/home/pages/conversation/components/input/MentionPicker.vue b/src/views/im/home/pages/conversation/components/input/MentionPicker.vue index 458996d80..1d49b8769 100644 --- a/src/views/im/home/pages/conversation/components/input/MentionPicker.vue +++ b/src/views/im/home/pages/conversation/components/input/MentionPicker.vue @@ -1,33 +1,69 @@ - diff --git a/src/views/im/home/pages/conversation/components/input/MessageInput.vue b/src/views/im/home/pages/conversation/components/input/MessageInput.vue index b7596a0a0..60957d053 100644 --- a/src/views/im/home/pages/conversation/components/input/MessageInput.vue +++ b/src/views/im/home/pages/conversation/components/input/MessageInput.vue @@ -2,12 +2,8 @@
- +
- - - + +
- - token 节点存在,删 token 即删 id,避免 stale atUserIds + - placeholder 通过 data-empty + ::before 模拟(contenteditable 没有原生 placeholder) + --> +
+ @scroll.passive="onEditorScroll" + @paste.prevent="onPaste" + >
@@ -96,9 +94,9 @@