需求描述:在输入框输入@后显示用户列表,实现@人功能
当前环境: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. valueif ( 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. valueconsole. 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. valueif ( editor) { editor. restoreSelection ( ) editor. deleteBackward ( 'character' ) editor. insertNode ( mentionNode) editor. move ( 1 ) }
}
const keyDown = ( e : any) => { const editor = editorRef. valueconsole. 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 } = firstOneinsertMentionHandler ( 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
就会影响到用户对话框的定位,所以根据富文本编辑器的光标来定位最好。