vue富文本wangeditor加@人功能(vue2 vue3都可以)

news2024/11/30 0:27:18

在这里插入图片描述

依赖

"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"@wangeditor/plugin-mention": "^1.0.0",

RichEditor.vue

<template>
  <div style="border: 1px solid #ccc; position: relative">
    <Editor
      style="height: 100px"
      :defaultConfig="editorConfig"
      v-model="valueHtml"
      @onCreated="handleCreated"
      @onChange="onChange"
    />
    <mention-modal
      v-if="isShowModal"
      @hideMentionModal="hideMentionModal"
      @insertMention="insertMention"
      :position="position"
      :list="list"
    ></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;
    list: any[];
  }>(),
  {
    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: '0px',
  bottom: '0px',
});
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, x: 1, y: 2 },
    job: '123',
    children: [{ text: '' }], // 必须有一个空 text 作为 children
  };
  const editor = editorRef.value;
  if (editor) {
    editor.restoreSelection(); // 恢复选区
    editor.deleteBackward('character'); // 删除 '@'
    console.log('node-', mentionNode);
    editor.insertNode(mentionNode, { abc: 'def' }); // 插入 mention

    editor.move(1); // 移动光标
  }
};

function getAtJobs() {
  return editorRef.value.children[0].children.filter((item: any) => item.type === 'mention').map((item: any) => item.info.id);
}
defineExpose({
  valueHtml,
  getAtJobs,
});
</script>

<style src="@wangeditor/editor/dist/css/style.css"></style>
<style scoped>
.w-e-scroll {
  max-height: 100px;
}
</style>

MentionModal.vue

<template>
  <div id="mention-modal" :style="{ 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="180px">
      <ul id="mention-list">
        <li v-for="item in searchedList" :key="item.id" @click="insertMentionHandler(item.id, item.username)">
          {{ item.username }}
        </li>
      </ul>
    </el-scrollbar>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, nextTick } from 'vue';

const props = defineProps<{
  position: any;
  list: 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: any = ref(props.list);
// 根据 <input> value 筛选 list
const searchedList = computed(() => {
  const searchValue = searchVal.value.trim().toLowerCase();
  return list.value.filter((item: any) => {
    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;
  bottom: -10px;
  border: 1px solid #ccc;
  background-color: #fff;
  padding: 5px;
  transition: all 0.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>

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

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

相关文章

python中字典的创建

1.字典的概念 字典是一种存储键值对的结构。 在python中能够根据键&#xff08;key&#xff09;来快速找到值&#xff08;value&#xff09; 根据key能够快速的找到value&#xff08;一对一的映射关系&#xff09; 在python的字典中&#xff0c;可以同时包含很多个键值对&am…

江协科技STM32学习- 2安装Keil5-MDK

本文是根据哔哩哔哩网站上“江协科技STM32”视频的学习笔记&#xff0c;在这里会记录下江协科技STM32开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了江协科技STM32教学视频和链接中的内容。 引用&#xff1a; STM32入门教程-2023版 细致讲解 中文字幕_哔哩哔哩…

基于小波脊线的一维时间序列信号分解方法(MATLAB R2018A)

信号分解技术是把一个复杂信号分解为若干含有时频信息的简单信号&#xff0c;研可通过分解后的简单信号来读取和分析复杂信号的有效特征。因此&#xff0c;信号分解技术对分析结果的影响是不言而喻的。 傅里叶分解是早期常用的信号分解方法&#xff0c;最初被用于分析热过程&a…

这些代码是APP自动化插件开发的关键!

在移动互联网高速发展的今天&#xff0c;APP的自动化插件开发成为了提升应用功能性和用户体验的重要手段。 而在这一过程中&#xff0c;五段源代码的巧妙运用往往能够起到事半功倍的效果&#xff0c;本文将为您科普分享这五段关键的源代码&#xff0c;帮助您更好地理解和应用自…

SJ708-II安全帽垂直间距配带高度测量仪

一、主要用途 依据GB/T2811-2007和GB/T2812-2006最新国家标准研发&#xff0c;主要用于安全帽垂直间距和配带高度试验&#xff0c;是安全帽生产企业办理生产许可证以及LA(劳安)认证&#xff0c;监督检测单位&#xff0c;科研机构必备安全帽检测设备。 二、仪器特征 1、采用铝…

3dmax材质高清参数设置图

3ds Max是一款在设计领域内非常受推崇的软件&#xff0c;以其强大的建模功能和丰富的材质库而知名。设计师可以通过调整材质的参数来制作出更加真实的渲染效果。本文将介绍一些技巧&#xff0c;教您如何通过简单的调整来优化3ds Max中的材质设置&#xff0c;从而增强作品的视觉…

【Linux】Linux环境基础开发工具_6

文章目录 四、Linux环境基础开发工具gdb 未完待续 四、Linux环境基础开发工具 gdb 我们已经可以写代码了&#xff0c;也能够执行代码了&#xff0c;但是代码错了该如何调试呢&#xff1f;Linux中可以使用 gdb 工具进行调试。 我们写一个简单的程序&#xff1a; 但是我们尝试…

机器学习笔记 - 用于3D数据分类、分割的Point Net的网络实现

上一篇,我们大致了解了Point Net的原理,这里我们要进行一下实现。 机器学习笔记 - 用于3D数据分类、分割的Point Net简述-CSDN博客文章浏览阅读3次。在本文中,我们将了解Point Net,目前,处理图像数据的方法有很多。从传统的计算机视觉方法到使用卷积神经网络到Transforme…

Swift 中更现代化的调试日志系统趣谈(一)

概述 昨天凌晨苹果刚刚发布了 WWDC2024 一系列新视频,这标志着苹果开发的一只脚已迈入人工智能(Apple Intelligence)的崭新时代。即便如此,我相信不少秃头码农们还在使用一些“远古简陋”的调试方法来剖析 2142 年的代码。 不过别担心,这一切将在小伙伴们学完本系列博文后…

Redis的一点入门了解

Redis NoSql概述 1、单机MySQL的时代 90年代&#xff0c;一个网站的访问量一般不会太大&#xff0c;单个数据库完全足够应付&#xff0c;技术上更多的会去使用静态页面html&#xff0c;对此服务器压根没多少压力&#xff1b; 但即使在这样的情况下&#xff0c;也会存在着一些…

doc 和 docx 文件的区别

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

几行代码实现多对多网格视图

当我们希望实现如下图所示效果如何实现呢: 我们可以使用Vis.js,vis.js Vis Network Examples Vis.js 是一个支持多种网络可视化的库,使用简单,功能强大。 以下是具体实现例子 不带箭头的: <!DOCTYPE html> <html> <head><meta charset="utf…

Windows 系统下 JDK 1.8 与 17 版本的相互切换

目录 一、当前本机已安装的 JDK 版本&#xff1a;1.8 二、下载 JDK 17 三、修改系统配置&#xff0c;将 JDK 版本切换为 17 1、新建 JAVA17_HOME 2、编辑 Path 3、验证是否切换成功 4、之后想再切换成 JDK 1.8 一、当前本机已安装的 JDK 版本&#xff1a;1.8 二、下载 J…

pyecharts画水球图

水波图图是一种适合于展现单个百分比数据的图表类型 from pyecharts.charts import Grid,Liquid from pyecharts.commons.utils import JsCodel1 (Liquid()#设置数据系列名称及数据.add("lq",[0.7,0.6,0.25],center["60%","50%"],color[blue,ye…

图像处理与视觉感知复习--空间域图像增强

文章目录 图像增强直方图空间滤波器 图像增强 图像增强 { 处理方法 { 空间域方法 { 点处理 ( 变换 ) 模板处理 ( 滤波 ) 频域方法 处理策略 { 全局处理 局部处理 处理对象 { 灰度图像 彩色图像 图像增强 \begin{cases} 处理方法 \begin{cases} 空间域方法 \begin{cases} 点处理…

Hadoop 2.0:主流开源云架构(三)

目录 四、Hadoop 2.0体系架构&#xff08;一&#xff09;Hadoop 2.0公共组件Common&#xff08;二&#xff09;分布式文件系统HDFS&#xff08;三&#xff09;分布式操作系统Yarn&#xff08;四&#xff09;Hadoop 2.0安全机制简介 四、Hadoop 2.0体系架构 &#xff08;一&…

查分易分班查询系统怎么做?

分班查询一直是让许多老师头疼的问题。一到开学季&#xff0c;办公桌上就堆满了学生的资料和分班表。要将这些信息一一录入系统&#xff0c;然后发布给学生和家长极其浪费时间和精力&#xff0c;而且很容易出错。每当分班结果公布时&#xff0c;家长和学生急切地想要知道自己的…

第20篇 Intel FPGA Monitor Program的使用<三>

Q&#xff1a;如何用Intel FPGA Monitor Program创建汇编语言工程呢&#xff1f; A&#xff1a;我们用一个Nios II汇编语言简易应用程序来发掘Intel Monitor FPGA Program软件的一些功能特性&#xff0c;并介绍创建工程的基本步骤。该程序可以实现找到存储在存储器中的32位整…

「51媒体-年中大促」天津有哪些媒体资源-媒体宣传服务公司

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 天津的媒体资源相当丰富&#xff0c;涵盖了报纸、电视、广播、新闻门户网站、央媒驻天津机构、视频媒体以及全国媒体资源等多个方面。以下是详细的媒体资源分类和具体信息&#xff1a; 一…

Excel 多列组合内容循环展开

某表格 A 列是编号&#xff0c;其他列是用逗号分隔的意义不同的分类列 ABCDEFG1Assembly#ProductTypeUnit ConfigNominal CapacitySupply VoltageGenerationCase Construction23H1012290001CMD,P24,36FAA,B33H1012290002CMD,P48,60FA,BA,B43H1012290003CMD,P24,36B,C,D,EAA,B …