feat(editor): 添加Ionicons4图标并优化编辑器功能
- 新增@vicons/ionicons4依赖用于编辑器发送按钮 - 优化提及列表滚动行为,保持选中项可见 - 支持Ctrl+Enter/Shift+Enter换行功能 - 添加发送按钮和编辑器placeholder提示 - 修复引用消息id字段不一致问题
This commit is contained in:
parent
1ff26564c7
commit
7067c42b2b
@ -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",
|
||||
|
@ -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))':
|
||||
|
@ -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);
|
||||
|
||||
// 处理Enter键发送消息(只有在没有按Ctrl/Cmd时才发送)
|
||||
if (event.key === 'Enter' && !event.ctrlKey && !event.metaKey) {
|
||||
// 处理Ctrl+Enter或Shift+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;
|
||||
}
|
||||
|
||||
// 处理Enter键发送消息(只有在没有按Ctrl/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,9 +1454,10 @@ const handleEditorClick = (event) => {
|
||||
* 使用flex布局排列工具按钮
|
||||
*/
|
||||
.tools {
|
||||
height: 100%;
|
||||
height: 40px;
|
||||
flex: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
/**
|
||||
* 单个工具按钮样式
|
||||
@ -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>
|
@ -46,9 +46,9 @@ export default defineConfig(({ mode }) => {
|
||||
vueJsx({}),
|
||||
compressPlugin(),
|
||||
UnoCSS(),
|
||||
vueDevTools({
|
||||
launchEditor: 'trae',
|
||||
})
|
||||
// vueDevTools({
|
||||
// launchEditor: 'trae',
|
||||
// })
|
||||
],
|
||||
define: {
|
||||
__APP_ENV__: env.APP_ENV
|
||||
|
Loading…
Reference in New Issue
Block a user