wangEditor5实现@评论功能

需求描述:在输入框输入@后显示用户列表,实现@人功能

当前环境: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: ''
})
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()

// const valueHtml = ref('

你好@A张三

')
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 // 记录 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', // 必须是 'mention' value: username, info: { id }, children: [{ text: '' }], // 必须有一个空 text 作为 children } const editor = editorRef.value if (editor) { editor.restoreSelection() // 恢复选区 editor.deleteBackward('character') // 删除 '@' editor.insertNode(mentionNode) // 插入 mention 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') // this.sendBut()//发送信息的方法 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 ''
})
// list 信息
const searchVal = ref('')
const tempList = Array.from({ length: 20 }).map((_, index) => {
  return {
    id: index,
    username: '张三' + index,
    account: 'wp'
  }
})
const list = ref(tempList)
// 根据  value 筛选 list
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) => {
  // esc - 隐藏 modal
  if (event.key === 'Escape') {
    emit('hideMentionModal')
  }

  // enter - 插入 mention node
  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') // 隐藏 modal
}
const input = ref()
onMounted(() => {
  // 获取光标位置
  // const domSelection = document.getSelection()
  // const domRange = domSelection?.getRangeAt(0)
  // if (domRange == null) return
  // const rect = domRange.getBoundingClientRect()

  // 定位 modal
  // top.value = props.position.top
  // left.value = props.position.left

  // focus input
  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就会影响到用户对话框的定位,所以根据富文本编辑器的光标来定位最好。

你可能感兴趣的:(javascript,vue.js,前端)