feat(editor): 添加Ionicons4图标并优化编辑器功能

- 新增@vicons/ionicons4依赖用于编辑器发送按钮
- 优化提及列表滚动行为,保持选中项可见
- 支持Ctrl+Enter/Shift+Enter换行功能
- 添加发送按钮和编辑器placeholder提示
- 修复引用消息id字段不一致问题
This commit is contained in:
Phoenix 2025-06-06 14:49:38 +08:00
parent 1ff26564c7
commit 7067c42b2b
4 changed files with 129 additions and 39 deletions

View File

@ -20,6 +20,7 @@
"@kangc/v-md-editor": "^2.3.18",
"@onlyoffice/document-editor-vue": "^1.5.0",
"@vicons/fluent": "^0.13.0",
"@vicons/ionicons4": "^0.13.0",
"@vicons/ionicons5": "^0.13.0",
"@vueup/vue-quill": "^1.2.0",
"@vueuse/core": "^10.7.0",

View File

@ -26,6 +26,9 @@ importers:
'@vicons/fluent':
specifier: ^0.13.0
version: 0.13.0
'@vicons/ionicons4':
specifier: ^0.13.0
version: 0.13.0
'@vicons/ionicons5':
specifier: ^0.13.0
version: 0.13.0
@ -1002,6 +1005,9 @@ packages:
'@vicons/fluent@0.13.0':
resolution: {integrity: sha512-bYGZsOE3qzvm3Cm43e7tybgGlr5ZUpYqtRZq0g0Tfupe8jIzLolpvQLNUt1zS8Mgt6goTbUk5YH7Fkv16jkykg==}
'@vicons/ionicons4@0.13.0':
resolution: {integrity: sha512-5WHIl/4R5a4i9GONa+hIQWxg/WczrbsCdqxawHZvdd3drsEr+Q3yzlfS+NNRO4WS3uDW2uWLCwoW+yp5TgcKeQ==}
'@vicons/ionicons5@0.13.0':
resolution: {integrity: sha512-zvZKBPjEXKN7AXNo2Na2uy+nvuv6SP4KAMQxpKL2vfHMj0fSvuw7JZcOPCjQC3e7ayssKnaoFVAhbYcW6v41qQ==}
@ -4411,6 +4417,8 @@ snapshots:
'@vicons/fluent@0.13.0': {}
'@vicons/ionicons4@0.13.0': {}
'@vicons/ionicons5@0.13.0': {}
'@vitejs/plugin-vue-jsx@3.1.0(vite@6.3.5(@types/node@18.19.99)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.2))(vue@3.5.13(typescript@5.2.2))':

View File

@ -15,7 +15,7 @@ import { uploadImg } from '@/api/upload'
import { defAvatar } from '@/constant/default'
import { getImageInfo } from '@/utils/functions'
import MeEditorEmoticon from './MeEditorEmoticon.vue'
import {IosSend} from '@vicons/ionicons4'
const props = defineProps({
vote: {
type: Boolean,
@ -24,6 +24,10 @@ const props = defineProps({
members: {
type: Array,
default: () => []
},
placeholder: {
type: String,
default: 'Enter-发送消息 [Ctrl+Enter/Shift+Enter]-换行'
}
})
@ -284,10 +288,34 @@ const handleKeydown = (event) => {
case 'ArrowUp':
event.preventDefault()
selectedMentionIndex.value = Math.max(0, selectedMentionIndex.value - 1)
// -
nextTick(() => {
const mentionList = document.querySelector('.mention-list ul')
const selectedItem = mentionList?.children[selectedMentionIndex.value]
if (mentionList && selectedItem) {
//
if (selectedItem.offsetTop < mentionList.scrollTop) {
mentionList.scrollTop = selectedItem.offsetTop
}
}
})
break
case 'ArrowDown':
event.preventDefault()
selectedMentionIndex.value = Math.min(mentionList.value.length - 1, selectedMentionIndex.value + 1)
// -
nextTick(() => {
const mentionList = document.querySelector('.mention-list ul')
const selectedItem = mentionList?.children[selectedMentionIndex.value]
if (mentionList && selectedItem) {
//
const itemBottom = selectedItem.offsetTop + selectedItem.offsetHeight
const listBottom = mentionList.scrollTop + mentionList.clientHeight
if (itemBottom > listBottom) {
mentionList.scrollTop = itemBottom - mentionList.clientHeight
}
}
})
break
case 'Enter':
case 'Tab':
@ -302,14 +330,44 @@ const handleKeydown = (event) => {
}
return
}
console.log('键盘事件:', event.key, 'Ctrl:', event.ctrlKey, 'Meta:', event.metaKey);
console.log('键盘事件:', event.key, 'Ctrl:', event.ctrlKey, 'Meta:', event.metaKey, 'Shift:', event.shiftKey);
// EnterCtrl/Cmd
if (event.key === 'Enter' && !event.ctrlKey && !event.metaKey) {
// Ctrl+EnterShift+Enter
if (event.key === 'Enter' && (event.ctrlKey || event.metaKey || event.shiftKey)) {
console.log('Ctrl+Enter或Shift+Enter换行');
//
//
const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const br = document.createElement('br');
range.deleteContents();
range.insertNode(br);
//
const textNode = document.createTextNode('');
range.setStartAfter(br);
range.insertNode(textNode);
range.setStartAfter(textNode);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
//
handleInput({ target: editorRef.value });
}
//
event.preventDefault();
return;
}
// EnterCtrl/Cmd/Shift
if (event.key === 'Enter' && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
console.log('Enter发送消息');
event.preventDefault()
console.log('editorContent.value', editorContent.value)
console.log('editorHtml.value', editorHtml.value)
event.preventDefault();
console.log('editorContent.value', editorContent.value);
console.log('editorHtml.value', editorHtml.value);
//
// handleInput editorContent.value
// if (editorContent.value.trim()) {
@ -356,7 +414,7 @@ const sendMessage = () => {
mentionUids: messageData.mentionUids,
quoteId: messageData.quoteId,
quoteData: quoteData.value ? {
msg_id: quoteData.value.msg_id,
id: quoteData.value.id,
title: quoteData.value.title,
describe: quoteData.value.describe,
image: quoteData.value.image
@ -405,6 +463,7 @@ const sendMessage = () => {
}else if(item.type === 4){
}
clearEditor()
})
@ -424,7 +483,7 @@ const parseEditorContent = () => {
let quoteInfo = null
if (quoteElements.length > 0 && quoteData.value) {
quoteInfo = {
msg_id: quoteData.value.msg_id,
id: quoteData.value.id,
title: quoteData.value.title,
describe: quoteData.value.describe,
image: quoteData.value.image
@ -1241,7 +1300,7 @@ const handleEditorClick = (event) => {
包含各种编辑工具按钮
-->
<header class="el-header toolbar bdr-t">
<div class="tools">
<div class="tools pr-30px">
<!--
表情选择器弹出框
使用n-popover组件创建悬浮的表情选择面板
@ -1292,6 +1351,14 @@ const handleEditorClick = (event) => {
<n-icon size="18" class="icon" :component="nav.icon" />
<p class="tip-title">{{ nav.title }}</p>
</div>
<n-button class="w-80px h-30px ml-auto" type="primary">
<template #icon>
<n-icon>
<IosSend />
</n-icon>
</template>
发送
</n-button>
</div>
</header>
@ -1325,37 +1392,25 @@ const handleEditorClick = (event) => {
通过v-if="showMention"控制显示/隐藏
使用动态样式定位列表位置
-->
<div
v-if="showMention"
class="mention-list py-5px"
class="mention-list py-5px"
:style="{ top: mentionPosition.top + 'px', left: mentionPosition.left + 'px' }"
>
<n-virtual-list style="max-height: 140px;width: 163px;" :item-size="42" :items="mentionList">
<template #default="{ item }">
<div :key="item.key" class="cursor-pointer px-14px h-42px hover:bg-#EEE9F9" @mousedown.prevent="insertMention(item)">
<div class="flex items-center border-b-1px border-b-solid border-b-#F8F8F8 flex items-center h-full ">
<img class="w-26px h-26px rounded-50% mr-11px" :src="item.avatar" alt="">
<span> {{ item.nickname }}</span>
</div>
</div>
</template>
</n-virtual-list>
<ul>
<!--
提及列表项
循环渲染mentionList数组中的用户
通过:class="{ selected: index === selectedMentionIndex }"高亮当前选中项
@mousedown.prevent防止失去焦点
-->
<!-- <li
<ul class="max-h-140px w-163px overflow-auto hide-scrollbar">
<li
v-for="(member, index) in mentionList"
:key="member.user_id"
:class="{ selected: index === selectedMentionIndex }"
:key="member.user_id || member.id"
class="cursor-pointer px-14px h-42px"
:class="{ 'bg-#EEE9F9': index === selectedMentionIndex }"
@mousedown.prevent="insertMention(member)"
@mouseover="selectedMentionIndex = index"
>
{{ member.nickname }}
</li> -->
<div class="flex items-center border-b-1px border-b-solid border-b-#F8F8F8 h-full">
<img class="w-26px h-26px rounded-50% mr-11px" :src="member.avatar" alt="">
<span>{{ member.nickname }}</span>
</div>
</li>
</ul>
</div>
</main>
@ -1399,10 +1454,11 @@ const handleEditorClick = (event) => {
* 使用flex布局排列工具按钮
*/
.tools {
height: 100%;
height: 40px;
flex: auto;
display: flex;
align-items: center;
/**
* 单个工具按钮样式
* 使用flex布局居中对齐内容
@ -1615,6 +1671,13 @@ const handleEditorClick = (event) => {
background: transparent;
overflow-y: auto;
/* 添加placeholder样式 */
&:empty:before {
content: attr(placeholder);
color: #999;
pointer-events: none;
}
/**
* 自定义滚动条样式 - 轨道
* 使用::-webkit-scrollbar伪元素自定义滚动条轨道
@ -1829,4 +1892,22 @@ html[theme-mode='dark'] {
--tip-bg-color: #48484d;
}
}
/**
* 隐藏滚动条样式
* 保留滚动功能但隐藏滚动条的视觉显示
*/
.hide-scrollbar {
/* Chrome, Safari, Edge */
&::-webkit-scrollbar {
width: 0;
display: none;
}
/* Firefox */
scrollbar-width: none;
/* IE */
-ms-overflow-style: none;
}
</style>

View File

@ -46,9 +46,9 @@ export default defineConfig(({ mode }) => {
vueJsx({}),
compressPlugin(),
UnoCSS(),
vueDevTools({
launchEditor: 'trae',
})
// vueDevTools({
// launchEditor: 'trae',
// })
],
define: {
__APP_ENV__: env.APP_ENV