需求描述:在输入框输入@后显示用户列表,实现@人功能
当前环境:vue3+vite+elementPlus+wangEditor@5
需要插件:@wangeditor/plugin-mention
安装插件:npm i @wangeditor/plugin-mention
输入框组件分两部分:1. wangEditor富文本编辑器部分,2. 用户列表对话框部分
1. 富文本编辑器组件代码:AutoComplete.vue
文件
< template>
< div style = " border : 1px solid #ccc; position : relative; " >
< Editor style = " height : 100px" :defaultConfig = " editorConfig" v-model = " valueHtml" @onCreated = " handleCreated"
@onChange = " onChange" @keydown.enter.native = " keyDown" />
< mention-modal v-if = " isShowModal" @hideMentionModal = " hideMentionModal" @insertMention = " insertMention"
:position = " position" > </ mention-modal>
</ div>
</ template>
< script setup lang = " ts" >
import { ref, shallowRef, onBeforeUnmount, nextTick, watch } from 'vue'
import { Boot } from '@wangeditor/editor'
import { Editor } from '@wangeditor/editor-for-vue'
import mentionModule from '@wangeditor/plugin-mention'
import MentionModal from './MentionModal.vue'
Boot. registerModule ( mentionModule)
const props = withDefaults ( defineProps< {
content? : string
} > ( ) , {
content : ''
} )
const editorRef = shallowRef ( )
const valueHtml = ref ( '' )
const isShowModal = ref ( false )
watch ( ( ) => props. content, ( val : string) => {
nextTick ( ( ) => {
valueHtml. value = val
} )
} )
onBeforeUnmount ( ( ) => {
const editor = editorRef. value
if ( editor == null ) return
editor. destroy ( )
} )
const position = ref ( {
left : '15px' ,
top : '40px'
} )
const handleCreated = ( editor : any) => {
editorRef. value = editor
position. value = editor. getSelectionPosition ( )
}
const showMentionModal = ( ) => {
nextTick ( ( ) => {
const editor = editorRef. value
console. log ( editor. getSelectionPosition ( ) ) ;
position. value = editor. getSelectionPosition ( )
} )
isShowModal. value = true
}
const hideMentionModal = ( ) => {
isShowModal. value = false
}
const editorConfig = {
placeholder : '请输入内容...' ,
EXTEND_CONF : {
mentionConfig : {
showModal : showMentionModal,
hideModal : hideMentionModal,
} ,
} ,
}
const onChange = ( editor : any) => {
console. log ( 'changed html' , editor. getHtml ( ) )
console. log ( 'changed content' , editor. children)
}
const insertMention = ( id : any, username : any) => {
const mentionNode = {
type : 'mention' ,
value : username,
info : { id } ,
children : [ { text : '' } ] ,
}
const editor = editorRef. value
if ( editor) {
editor. restoreSelection ( )
editor. deleteBackward ( 'character' )
editor. insertNode ( mentionNode)
editor. move ( 1 )
}
}
const keyDown = ( e : any) => {
const editor = editorRef. value
console. log ( editor. children[ 0 ] . children. filter ( ( item : any) => item. type === 'mention' ) . map ( ( item : any) => item. info. id) , 'key === 发song' )
if ( e != undefined ) {
e. preventDefault ( ) ;
}
}
</ script>
< style src = " @wangeditor/editor/dist/css/style.css" > </ style>
< style scoped >
.w-e-scroll {
max-height : 100px;
}
</ style>
2. 用户列表对话框 MentionModal.vue
文件
< template>
< div id = " mention-modal" :style = " { top, left, right, bottom }" >
< el-input id = " mention-input" v-model = " searchVal" ref = " input" @keyup = " inputKeyupHandler" onkeypress = " if(event.keyCode === 13) return false" placeholder = " 请输入用户名搜索" />
< el-scrollbar height = " 200px" >
< ul id = " mention-list" >
< li v-for = " item in searchedList" :key = " item.id" @click = " insertMentionHandler(item.id, item.username)" > {{
item.username }}({{ item.account }})
</ li>
</ ul>
</ el-scrollbar>
</ div>
</ template>
< script setup lang = " ts" >
import { ref, computed, onMounted, nextTick } from 'vue'
const props = defineProps< {
position : any
} > ( )
const emit = defineEmits ( [ 'hideMentionModal' , 'insertMention' ] )
const top = computed ( ( ) => {
return props. position. top
} )
const bottom = computed ( ( ) => {
return props. position. bottom
} )
const left = computed ( ( ) => {
return props. position. left
} )
const right = computed ( ( ) => {
if ( props. position. right) {
const right = + ( props. position. right. split ( 'px' ) [ 0 ] ) - 180
return right < 0 ? 0 : ( right + 'px' )
}
return ''
} )
const searchVal = ref ( '' )
const tempList = Array. from ( { length : 20 } ) . map ( ( _, index ) => {
return {
id : index,
username : '张三' + index,
account : 'wp'
}
} )
const list = ref ( tempList)
const searchedList = computed ( ( ) => {
const searchValue = searchVal. value. trim ( ) . toLowerCase ( )
return list. value. filter ( item => {
const username = item. username. toLowerCase ( )
if ( username. indexOf ( searchValue) >= 0 ) {
return true
}
return false
} )
} )
const inputKeyupHandler = ( event : any) => {
if ( event. key === 'Escape' ) {
emit ( 'hideMentionModal' )
}
if ( event. key === 'Enter' ) {
const firstOne = searchedList. value[ 0 ]
if ( firstOne) {
const { id, username } = firstOne
insertMentionHandler ( id, username)
}
}
}
const insertMentionHandler = ( id : any, username : any) => {
emit ( 'insertMention' , id, username)
emit ( 'hideMentionModal' )
}
const input = ref ( )
onMounted ( ( ) => {
nextTick ( ( ) => {
input. value?. focus ( )
} )
} )
</ script>
< style>
#mention-modal {
position : absolute;
border : 1px solid #ccc;
background-color : #fff;
padding : 5px;
transition : all .3s;
}
#mention-modal input {
width : 150px;
outline : none;
}
#mention-modal ul {
padding : 0;
margin : 5px 0 0;
}
#mention-modal ul li {
list-style : none;
cursor : pointer;
padding : 5px 2px 5px 10px;
text-align : left;
}
#mention-modal ul li:hover {
background-color : #f1f1f1;
}
</ style>
注意:对话框的定位是根据编辑器editor.getSelectionPosition()
来确定的,因为我发现,当页面出现滚动时,根据页面获取光标定位不是很准确。 还有,如果你页面组件嵌套多层的话,其中有一个设置了relative
就会影响到用户对话框的定位,所以根据富文本编辑器的光标来定位最好。