From 1b30582dd9aff67be82cbe155c1a12762081855d Mon Sep 17 00:00:00 2001 From: kuaifan Date: Fri, 26 Sep 2025 20:18:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0emoji=E8=A1=A8?= =?UTF-8?q?=E6=83=85=E5=88=A0=E9=99=A4=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../manage/components/ChatInput/emoji.vue | 26 ++++++- .../manage/components/ChatInput/index.vue | 70 ++++++++++++++++++- .../sass/pages/components/chat-input.scss | 26 +++++++ 3 files changed, 117 insertions(+), 5 deletions(-) diff --git a/resources/assets/js/pages/manage/components/ChatInput/emoji.vue b/resources/assets/js/pages/manage/components/ChatInput/emoji.vue index 789ff7f0c..2a36eb49c 100644 --- a/resources/assets/js/pages/manage/components/ChatInput/emoji.vue +++ b/resources/assets/js/pages/manage/components/ChatInput/emoji.vue @@ -16,13 +16,26 @@ +
+ + + + +
@@ -52,6 +65,11 @@ export default { onlyEmoji: { type: Boolean, default: false + }, + // 是否显示 emoji 删除按钮 + showEmojiDelete: { + type: Boolean, + default: false } }, data() { @@ -222,6 +240,10 @@ export default { } }, + onDelete() { + this.$emit('on-delete'); + }, + onMonitorWheel() { const container = this.$refs['chatEmojiMenuRef']; container?.addEventListener("wheel", (event) =>{ diff --git a/resources/assets/js/pages/manage/components/ChatInput/index.vue b/resources/assets/js/pages/manage/components/ChatInput/index.vue index 5ef25d0cc..9a4c8c4c0 100755 --- a/resources/assets/js/pages/manage/components/ChatInput/index.vue +++ b/resources/assets/js/pages/manage/components/ChatInput/index.vue @@ -227,7 +227,12 @@
- + @@ -414,6 +419,7 @@ export default { quill: null, isFocus: false, rangeIndex: 0, + rangeLength: 0, _content: '', _options: {}, @@ -868,9 +874,13 @@ export default { if (this.quill) { const range = this.quill.selection.savedRange; this.rangeIndex = range ? range.index : 0 + this.rangeLength = range ? range.length : 0 + } + } else { + this.rangeLength = 0; + if (this.rangeIndex > 0) { + this.quill.setSelection(this.rangeIndex) } - } else if (this.rangeIndex > 0) { - this.quill.setSelection(this.rangeIndex) } }, @@ -1741,6 +1751,8 @@ export default { if (item.type === 'emoji') { this.quill.insertText(this.rangeIndex, item.text); this.rangeIndex += item.text.length + this.rangeLength = 0; + this.quill.setSelection(this.rangeIndex, 0, 'silent'); if (this.windowLandscape && !this.isModKey) { this.showEmoji = false; } @@ -1755,6 +1767,58 @@ export default { } }, + onEmojiDelete() { + if (!this.quill) { + return; + } + const savedRange = this.quill.selection?.savedRange || this.quill.getSelection(); + if (savedRange && typeof savedRange.index === 'number') { + this.rangeIndex = savedRange.index; + this.rangeLength = savedRange.length || 0; + } + if (this.rangeLength > 0) { + this.quill.deleteText(this.rangeIndex, this.rangeLength); + this.rangeLength = 0; + } else if (this.rangeIndex > 0) { + const deleteLength = this.getPreviousGraphemeLength(this.rangeIndex); + if (deleteLength > 0) { + this.quill.deleteText(this.rangeIndex - deleteLength, deleteLength); + this.rangeIndex -= deleteLength; + } + } + this.quill.setSelection(this.rangeIndex, 0, 'silent'); + }, + + getPreviousGraphemeLength(index) { + if (!this.quill || index <= 0) { + return 0; + } + const textBeforeCursor = this.quill.getText(0, index); + if (!textBeforeCursor) { + return 0; + } + if (typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function') { + if (!this.graphemeSegmenter) { + this.graphemeSegmenter = new Intl.Segmenter(undefined, {granularity: 'grapheme'}); + } + let lastSegment; + for (const segment of this.graphemeSegmenter.segment(textBeforeCursor)) { + lastSegment = segment; + } + if (lastSegment && lastSegment.segment) { + return lastSegment.segment.length; + } + } + const fallbackWindow = Math.min(index, 8); + const fallbackText = this.quill.getText(index - fallbackWindow, fallbackWindow); + if (!fallbackText) { + return 0; + } + const fallbackGraphemes = Array.from(fallbackText); + const lastGrapheme = fallbackGraphemes.pop(); + return lastGrapheme ? lastGrapheme.length : 0; + }, + onToolbar(action) { this.hidePopover(); switch (action) { diff --git a/resources/assets/sass/pages/components/chat-input.scss b/resources/assets/sass/pages/components/chat-input.scss index d1634221b..3535f5307 100755 --- a/resources/assets/sass/pages/components/chat-input.scss +++ b/resources/assets/sass/pages/components/chat-input.scss @@ -511,6 +511,28 @@ flex-direction: column; position: relative; + .chat-emoji-delete-btn { + position: absolute; + right: 12px; + bottom: 60px; + border: none; + padding: 10px 14px; + border-radius: 8px; + background: #eee; + color: #666; + font-size: 20px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + outline: none; + + > svg { + width: 24px; + height: 24px; + } + } + .chat-emoji-emosearch { flex-shrink: 0; padding: 8px 8px 0; @@ -1298,6 +1320,10 @@ body.window-portrait { height: 50px; line-height: 50px; font-size: 28px; + + &.delete-placeholder { + height: 60px; + } } &.emosearch, &.emoticon {