From 8be935cf09883b5d4c54acde98259851280522cc Mon Sep 17 00:00:00 2001 From: KUN1007 Date: Fri, 8 Sep 2023 22:13:36 +0800 Subject: [PATCH] feat: quill module Mention --- package.json | 1 + pnpm-lock.yaml | 9 ++ src/components/KUNGalgameSearchBox.vue | 6 +- src/components/quill-editor/QuillEditor.vue | 140 ++++++++++++++++++- src/styles/editor/editor.module.mention.scss | 56 ++++++++ src/types/quill-mention.d.ts | 1 + 6 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 src/styles/editor/editor.module.mention.scss create mode 100644 src/types/quill-mention.d.ts diff --git a/package.json b/package.json index 24233890..9de72812 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "quill-blot-formatter": "^1.0.5", "quill-image-uploader": "^1.3.0", "quill-magic-url": "^4.2.0", + "quill-mention": "^3.4.1", "vue": "^3.3.4", "vue-i18n": "^9.2.2", "vue-router": "^4.2.4" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9fcf830d..7e6e916f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ dependencies: quill-magic-url: specifier: ^4.2.0 version: 4.2.0 + quill-mention: + specifier: ^3.4.1 + version: 3.4.1 vue: specifier: ^3.3.4 version: 3.3.4 @@ -1681,6 +1684,12 @@ packages: quill-delta: 3.6.3 dev: false + /quill-mention@3.4.1: + resolution: {integrity: sha512-7/KyWGDbh3T2pw/kPNhXrsUkc1j2Kp9mxtK7QqmU84A6oHIlcjCubsFntHQYPwX/xoJnjyzcWiBrig8vEgW0AA==} + dependencies: + quill: 1.3.7 + dev: false + /quill@1.3.7: resolution: {integrity: sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==} dependencies: diff --git a/src/components/KUNGalgameSearchBox.vue b/src/components/KUNGalgameSearchBox.vue index 992cca55..7608dc96 100644 --- a/src/components/KUNGalgameSearchBox.vue +++ b/src/components/KUNGalgameSearchBox.vue @@ -136,9 +136,9 @@ const handleDeleteHistory = (historyIndex: number) => { @click="handleClickHistory(index)" > {{ history }} - + + + diff --git a/src/components/quill-editor/QuillEditor.vue b/src/components/quill-editor/QuillEditor.vue index 0559ac80..75a7126b 100644 --- a/src/components/quill-editor/QuillEditor.vue +++ b/src/components/quill-editor/QuillEditor.vue @@ -9,6 +9,9 @@ import BlotFormatter from 'quill-blot-formatter' import ImageUploader from 'quill-image-uploader' // 引入 module: URL、邮箱 自动识别 import MagicUrl from 'quill-magic-url' +// 引入 module: mention +import Mention from 'quill-mention' +import '@/styles/editor/editor.snow.scss' // 自定义 quill 的两个主题,第二个主题暂时懒得动 import '@/styles/editor/editor.snow.scss' @@ -30,7 +33,6 @@ import { storeToRefs } from 'pinia' import DOMPurify from 'dompurify' // 导入防抖函数 import { debounce } from '@/utils/debounce' -import kungalgame from '@/router/modules/kungalgame' const { editorHeight, mode, theme, isSave, content } = storeToRefs( useKUNGalgameEditStore() @@ -56,6 +58,12 @@ const props = defineProps<{ // 编辑器实例 const editorRef = ref() +// 定义提及源数据的接口 +interface MentionItem { + id: number + value: string +} + // 编辑器 modules const modules = [ // BlotFormatter @@ -100,8 +108,77 @@ const modules = [ /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, }, }, + // Mention + { + name: 'mention', + module: Mention, + options: { + // 允许的输入搜索字符 + // allowedChars: /^[A-Za-z\s]*$/, + // 触发 mention 操作的关键词 + mentionDenotationChars: ['@', '#'], + /** + * @param {string} searchTerm - 用户输入的搜索关键词 + * @param {(items: MentionItem[]) => void} renderList - 渲染列表回调函数,需传入从后端获取的搜索结果数组 + * @param {string} mentionChar - 触发 mention 操作的关键词 + */ + source: async function ( + searchTerm: string, + renderList: (items: MentionItem[]) => void, + mentionChar: string + ) { + // 根据 mentionChar 的不同值执行不同的搜索逻辑 + if (mentionChar === '@') { + // 执行对用户的搜索 + const matchedUsers: MentionItem[] = await searchUsers(searchTerm) + renderList(matchedUsers) + } else if (mentionChar === '#') { + // 执行对话题的搜索 + const matchedTopics: MentionItem[] = await searchTopics(searchTerm) + renderList(matchedTopics) + } + }, + }, + }, ] +// 模拟搜索用户的函数 +async function searchUsers(searchTerm: string): Promise { + // 实际搜索逻辑 + // 返回匹配的用户列表 + return [ + { id: 1, value: 'kun' }, + { id: 2, value: 'yuyu' }, + { id: 3, value: 'kun' }, + { id: 4, value: 'yuyu' }, + { id: 5, value: 'kun' }, + { id: 6, value: 'yuyu' }, + { id: 7, value: 'kun' }, + { id: 8, value: 'yuyu' }, + { id: 9, value: 'kun' }, + { id: 10, value: 'yuyu' }, + { id: 11, value: 'kun' }, + { id: 12, value: 'yuyu' }, + { id: 13, value: 'kun' }, + { id: 14, value: 'yuyu' }, + { id: 15, value: 'kun' }, + { id: 16, value: 'yuyu' }, + ] +} + +// 模拟搜索话题的函数 +async function searchTopics(searchTerm: string): Promise { + // 实际搜索逻辑 + // 返回匹配的话题列表 + return [ + { + id: 1, + value: '啊这可海星啊这可海星啊这可海星啊这可海星啊这可海星啊这可海星', + }, + { id: 2, value: '鲲最可爱' }, + ] +} + // 编辑器的高度 const editorHeightStyle = computed( () => `height: ${props.height ? props.height : editorHeight.value}px` @@ -250,7 +327,7 @@ const handleTextChange = async () => { } } - /* BlotFormatter 插件的样式 */ + /* BlotFormatter 插件的样式,这里不用 !important 不行 */ .blot-formatter__toolbar-button { margin: 0 5px; border: none !important; @@ -265,5 +342,64 @@ const handleTextChange = async () => { background: var(--kungalgame-trans-blue-1) !important; } } + + /* Mention 的样式 */ + .ql-mention-list-container { + width: 270px; + border: 1px solid var(--kungalgame-blue-1); + border-radius: 4px; + background-color: var(--kungalgame-trans-white-2); + box-shadow: var(--shadow); + z-index: 9999; + overflow: auto; + } + + .ql-mention-loading { + line-height: 44px; + padding: 0 20px; + vertical-align: middle; + font-size: 16px; + } + + .ql-mention-list { + list-style: none; + margin: 0; + padding: 0; + overflow: hidden; + } + + .ql-mention-list-item { + cursor: pointer; + font-size: 16px; + padding: 10px 20px; + /* 居中、超过一行省略 */ + vertical-align: middle; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .ql-mention-list-item.disabled { + cursor: auto; + } + + .ql-mention-list-item.selected { + background-color: var(--kungalgame-trans-blue-1); + text-decoration: none; + } + + .mention { + height: 24px; + width: 65px; + border-radius: 6px; + background-color: var(--kungalgame-trans-blue-1); + padding: 3px 0; + margin-right: 2px; + user-select: all; + } + + .mention > span { + margin: 0 3px; + } } diff --git a/src/styles/editor/editor.module.mention.scss b/src/styles/editor/editor.module.mention.scss new file mode 100644 index 00000000..33c50a33 --- /dev/null +++ b/src/styles/editor/editor.module.mention.scss @@ -0,0 +1,56 @@ +/* 参考 'quill-mention/dist/quill.mention.css' */ + +.ql-mention-list-container { + width: 270px; + border: 1px solid var(--kungalgame-blue-1); + border-radius: 4px; + background-color: var(--kungalgame-trans-white-2); + box-shadow: var(--shadow); + z-index: 9001; + overflow: auto; +} + +.ql-mention-loading { + line-height: 44px; + padding: 0 20px; + vertical-align: middle; + font-size: 16px; +} + +.ql-mention-list { + list-style: none; + margin: 0; + padding: 0; + overflow: hidden; +} + +.ql-mention-list-item { + cursor: pointer; + line-height: 44px; + font-size: 16px; + padding: 0 20px; + vertical-align: middle; +} + +.ql-mention-list-item.disabled { + cursor: auto; +} + +.ql-mention-list-item.selected { + background-color: var(--kungalgame-trans-blue-0); + text-decoration: none; +} + +.mention { + height: 24px; + width: 65px; + border-radius: 6px; + background-color: var(--kungalgame-trans-blue-0); + padding: 3px 0; + margin-right: 2px; + user-select: all; +} + +.mention > span { + margin: 0 3px; +} diff --git a/src/types/quill-mention.d.ts b/src/types/quill-mention.d.ts new file mode 100644 index 00000000..0ccbf507 --- /dev/null +++ b/src/types/quill-mention.d.ts @@ -0,0 +1 @@ +declare module 'quill-mention' {}