diff --git a/package.json b/package.json index 8c5366b..0b8bf58 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 168cebb..e186607 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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))': diff --git a/src/components/editor/CustomEditor.vue b/src/components/editor/CustomEditor.vue index 335f632..fe75735 100644 --- a/src/components/editor/CustomEditor.vue +++ b/src/components/editor/CustomEditor.vue @@ -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) => { 包含各种编辑工具按钮 -->
-
+
-
- - - -
    - - +
    + + {{ member.nickname }} +
    +
@@ -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; +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 83a792e..715bc77 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -46,9 +46,9 @@ export default defineConfig(({ mode }) => { vueJsx({}), compressPlugin(), UnoCSS(), - vueDevTools({ - launchEditor: 'trae', - }) + // vueDevTools({ + // launchEditor: 'trae', + // }) ], define: { __APP_ENV__: env.APP_ENV