wangEditor5 implements @ comment function

Requirement description: Display the user list after entering @ in the input box to realize the @ person function

Please add image description

Current environment: vue3+vite+elementPlus+wangEditor@5

Requires plugins:@wangeditor/plugin-mention

Install plugin:npm i @wangeditor/plugin-mention

The input box component is divided into two parts: 1. wangEditor rich text editor part, 2. User list dialog part

1. Rich text editor component code: AutoComplete.vuefile

<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. User list dialog MentionModal.vuefile

<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()Note: The positioning of the dialog box is determined based on the editor because I found that getting the cursor positioning based on the page is not very accurate when scrolling occurs.
  • Also, if your page components are nested in multiple layers, setting one of them relativewill affect the positioning of the user dialog box, so it is best to position it according to the cursor of the rich text editor.

Guess you like

Origin blog.csdn.net/weixin_42566993/article/details/132250206