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 v-html="valueHtml"></div> -->
  </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('<p>你好<span data-w-e-type="mention" data-w-e-is-void data-w-e-is-inline data-value="A张三" data-info="%7B%22id%22%3A%22a%22%7D">@A张三</span></p>')
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)
// 根据 <input> 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就会影响到用户对话框的定位,所以根据富文本编辑器的光标来定位最好。

猜你喜欢

转载自blog.csdn.net/weixin_42566993/article/details/132250206
今日推荐