wangEditor5实现@评论功能

news2025/1/15 23:56:24

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

请添加图片描述

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/869462.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【electron】electron安装过慢和打包报错:Unable to load file:

文章目录 一、安装过慢问题:二、打包报错&#xff1a;Unable to load file: 一、安装过慢问题: 一直处于安装过程 【解决】 #修改npm的配置文件 npm config edit#添加配置 electron_mirrorhttps://cdn.npm.taobao.org/dist/electron/二、打包报错&#xff1a;Unable to load…

CAS服务端入门使用实践

CAS服务端入门使用实践 一、前言 1.简介 CAS 是一个企业多语言单点登录解决方案&#xff0c;支持大量附加身份验证协议和功能&#xff0c;满足身份验证和授权需求的综合平台。 2.环境 Windows 10JDK 1.8git version 2.41.0.windows.3Tomcat 9.0.78Maven 3.5.3cas-overlay-…

类与对象(加深)

目录 1.类的6个默认成员函数 2. 构造函数 2.1 概念 2.2 特性 3.析构函数 3.1 概念 3.2 特性 4. 拷贝构造函数 4.1 概念 4.2 特征 5.赋值运算符重载 5.1 运算符重载 5.2 赋值运算符重载 6.const成员 7.取地址及const取地址操作符重载 1.类的6个默认成员函数 如果…

51.C++继承

今天进行了新的学习关于c继承的知识。 目录 1.继承 基类and派生类 访问控制和继承 单继承 多继承 2.同名隐藏 1.继承 在C中&#xff0c;继承是一种面向对象编程的重要特性&#xff0c;用于构建类之间的层次关系。通过继承&#xff0c;一个类可以从另一个类继承其…

QT网络编程之TCP

QT网络编程之TCP TCP 编程需要用到俩个类: QTcpServer 和 QTcpSocket。 #------------------------------------------------- # # Project created by QtCreator 2023-08-

2023-08-12 LeetCode每日一题(合并 K 个升序链表)

2023-08-12每日一题 一、题目编号 23. 合并 K 个升序链表二、题目链接 点击跳转到题目位置 三、题目描述 给你一个链表数组&#xff0c;每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中&#xff0c;返回合并后的链表。 示例 1&#xff1a; 示例 2&…

String(字符串)

1、String概述 java.lang.String类代表字符串&#xff0c;Java程序中的所有字符串文字&#xff08;例如“abc”&#xff09;都为此类的对象。 1.1、String的注意点 字符串的内容是不会发生改变的&#xff0c;它的对象在创建后不能被更改。 1.2、总结 String是Java定义好的一个类…

LeetCode 34题:在排序数组中查找元素的第一个和最后一个位置

目录 题目 思路 代码 C语言 Python 题目 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(…

取证--理论

资料&#xff1a; 各比赛 Writeup &#xff1a; https://meiyacup.cn/Mo_index_gci_36.html 哔站比赛复盘视频&#xff1a; https://space.bilibili.com/453117423?spm_id_from333.337.search-card.all.click 自动分析取证四部曲 新建案例添加设备自动取证制作报告 取证大…

腾讯云CVM服务器竞价实例是什么?和按量计费有什么区别?

腾讯云服务器CVM计费模式分为包年包月、按量计费和竞价实例&#xff0c;什么是竞价实例&#xff1f;竞价实例和按量付费相类似&#xff0c;优势是价格更划算&#xff0c;缺点是云服务器实例有被自动释放风险&#xff0c;腾讯云服务器网来详细说下什么是竞价实例&#xff1f;以及…

全志T113-S3 Tina-linux --1. 开发环境搭建

1. 硬件环境 1.1 开发板 型号&#xff1a;100ASK_T113-PRO Base V1.1&#xff08;韦东山&#xff09;配置&#xff1a;CPU&#xff1a;T113-S3&#xff0c;RAM&#xff1a;128MB&#xff0c;ROM&#xff1a;128MB T113-S3配置 1.2 上手使用 1.2.1 串口shell 串口shell配置…

MySQL8安装教程 保姆级(Windows))

下载 官网: mysql官网点击Downloads->MySQL Community(GPL) Downloads->MySQL Community Server(或者点击MySQL installer for Windows) Windows下有两种安装方式 在线安装 一般带有 web字样 这个需要联网离线安装 一般没有web字样 安装 下载好之后,版本号可以不一样&…

【积水成渊】9 个CSS 伪元素

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人_python人工智能视觉&#xff08;opencv&#xff09;从入门到实战,前端,微信小程序-CSDN博客 最新的uniapp毕业设计专栏也放在下方了&#xff1a; https://blog.csdn.net/lbcy…

预测知识 | 预测技术流程及模型评价

预测知识 | 预测技术流程及模型评价 目录 预测知识 | 预测技术流程及模型评价技术流程模型评价参考资料 技术流程 1&#xff09;模型训练阶段&#xff1a;预测因素和结局&#xff0c;再加上预测模型进行模型拟合&#xff1b; 2&#xff09;预测阶段&#xff1a;将预测因素代入拟…

如何自学(黑客)网络安全

前言&#xff1a; 想自学网络安全&#xff08;黑客技术&#xff09;首先你得了解什么是网络安全&#xff01;什么是黑客&#xff01; 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“…

前端一键升级 package.json里面的依赖包管理

升级需谨慎 前端一键升级 package.json里面的依赖包管理 安装&#xff1a;npm-check-updates npm i npm-check-updates -g缩写 ncu 在项目根目录里面执行 ncu 如图&#xff1a;

日撸java_day61-62

决策树 package machineLearning.decisiontree;import weka.core.Instance; import weka.core.Instances;import java.io.FileReader; import java.util.Arrays;/*** ClassName: ID3* Package: machineLearning.decisiontree* Description: The ID3 decision tree inductive …

企业服务器数据库遭到malox勒索病毒攻击后如何解决,勒索病毒解密

网络技术的发展不仅为企业带来了更高的效率&#xff0c;还为企业带来信息安全威胁&#xff0c;其中较为常见的就是勒索病毒攻击。近期&#xff0c;我们公司收到很多企业的求助&#xff0c;企业的服务器数据库遭到了malox勒索病毒攻击&#xff0c;导致系统内部的许多重要数据被加…

YOLOv5基础知识入门(5)— 损失函数(IoU、GIoU、DIoU、CIoU和EIoU)

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。使用YOLOv5训练模型阶段&#xff0c;需要用到损失函数。损失函数是用来衡量模型预测值和真实值不一样的程度&#xff0c;极大程度上决定了模型的性能。本节就给大家介绍IoU系列损失函数&#xff0c;希望大家学习之后能够有…

分布式 - 消息队列Kafka:Kafka消费者的分区分配策略

文章目录 1. 环境准备2. range 范围分区策略介绍3. round-robin 轮询分区策略4. sticky 粘性分区策略5. 自定义分区分配策略 1. 环境准备 创建主题 test 有5个分区&#xff0c;准备 3 个消费者并进行消费&#xff0c;观察消费分配情况。然后再停止其中一个消费者&#xff0c;再…