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

在这里插入图片描述

依赖

"@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>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

表 达式树

》》》可以借助 LINQPad工具 using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; using System.Transactions;namespace EFDemo {public cla…

深入解析TF-IDF算法:文本分析的基石与力量

在信息爆炸的时代文本数据无处不在&#xff0c;从新闻报道到社交媒体帖子&#xff0c;从学术论文到产品评论&#xff0c;大量的文本信息需要被有效地分析和利用。在这样的背景下TF-IDF&#xff08;Term Frequency-Inverse Document Frequency&#xff09;算法作为一种简单而有效…

【AI基础】第五步:纯天然保姆喂饭级-安装并运行chatglm3-6b

类似于 【AI基础】第三步&#xff1a;纯天然保姆喂饭级-安装并运行chatglm2-6b&#xff0c;有一些细节不一样。 此系列文章列表&#xff1a; 【AI基础】第一步&#xff1a;安装python开发环境-windows篇_下载安装ai环境python 【AI基础】第一步&#xff1a;安装python开发环境-…

C++:十大排序

目录 时间复杂度分析 选择排序 引言 算法思想 动图展示 代码实现 (升序) 优化 代码实现 分析 冒泡排序 引言 算法思想 动图展示 代码实现 插入排序 引言 算法思想 动图展示 代码实现 计数排序 引言 算法思想 动图展示 代码实现 桶排序 引言 算法思…

OpenCV计算形状之间的相似度ShapeContextDistanceExtractor类的使用

操作系统&#xff1a;ubuntu22.04OpenCV版本&#xff1a;OpenCV4.9IDE:Visual Studio Code编程语言&#xff1a;C11 1.功能描述 ShapeContextDistanceExtractor是OpenCV库中的一个类&#xff0c;主要用于计算形状之间的相似度或距离。它是基于形状上下文&#xff08;Shape Co…

vue页面和 iframe多页面无刷新方案和并行存在解决方案

面临问题 : back的后台以jsp嵌套iframe为主, 所以在前端框架要把iframe无刷新嵌套和vue页面进行并行使用,vue的keep-alive只能对虚拟dom树 vtree 进行缓存无法缓存iframe,所以要对iframe进行处理 tab标签的切换效果具体参考若依框架的tab切换,可以去若依看源码,若依源码没有实…

C语言| 数组

直接定义一个数组&#xff0c;并给所有元素赋值。 数组的下标从0开始&#xff0c;下标又表示数组的长度。 【程序代码】 #include <stdio.h> int main(void) { int a[5] {1, 2, 3, 4, 5}; int i; for(i0; i<5; i) { printf("a[%d] %d\…

EulerOS 安装docker 拉取opengauss 镜像

#下载docker包 wget https://download.docker.com/linux/static/stable/x86_64/docker-18.09.9.tgz #解压 tar zxf docker-18.09.9.tgz #移动解压后的文件夹到/usr/bin mv docker/* /usr/bin #写入docker.service cat >/usr/lib/systemd/system/docker.service <<E…

在不使用js在情况下只用css实现瀑布流效果

使用到的是grid 布局&#xff0c;需要注意的是grid-template-rows: masonry; 目前只有Firefox 浏览器支持这个效果&#xff0c;而且还是一个实验性属性需要在设置里面开发实验性选项才行。 实例 <!DOCTYPE html> <html> <head><title>Document</ti…

如何安装和配置JDK?(详细步骤分享)

1、下载JDK 访问Oracle官方网站&#xff08;Oracle | Cloud Applications and Cloud Platform&#xff09;&#xff0c;选择适合您操作系统的JDK版本进行下载。建议下载最新的稳定版本。 打开Java&#xff0c;往下拉&#xff0c;找到Oracle JDK 打开后&#xff0c;选择右边的J…

抖店被扣保证金,做起来太难导致心态崩了,怎么办?

我是王路飞。 技术、黑科技这些东西&#xff0c;决定不了你做店的结果。 能够决定最终结果的&#xff0c;一定是心态&#xff0c;是乐观还是悲观&#xff1f;是自负还是自卑&#xff1f;是焦躁还是踏实&#xff1f;这很关键。 店铺被扣保证金了&#xff0c;感觉没希望了&…

Vue22-v-model收集表单数据

一、效果图 二、代码 2-1、HTML代码 2-2、vue代码 1、v-model单选框的收集信息 v-model&#xff1a;默认收集的就是元素中的value值。 单选框添加默认值&#xff1a; 2、v-model多选框的收集信息 ①、多个选择的多选 注意&#xff1a; 此处的hobby要是数组&#xff01;&…

视频媒介VS文字媒介

看到一篇蛮有思考意义的文章就摘录下来了&#xff0c;也引起了反思 目录 一、视频的定义 二、”视频媒介“与”文字媒介”作对比 1.形象 VS 抽象 2.被动 VS 主动 三、视频的缺点-【更少】的思考 1.看视频为啥会导致【更少的思考】 2.内容的【浅薄化】 3.内容的【娱乐化…

ctfshow-web入门-命令执行(web43-web52)关于黑洞“ >/dev/null 2>1“的处理与绕过

目录 1、web43 2、web44 3、web45 4、web46 5、web47 6、web48 7、web49 8、web50 9、web51 10、web52 1、web43 在上一题 ‘黑洞’ 的基础上新增过滤&#xff1a; preg_match("/\;|cat/i", $c) 问题不大&#xff0c;我们不用分号和 cat 就行&#xff1a;…

【动态规划算法题记录】70. 爬楼梯——递归/动态规划

题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 题目分析 递归法&#xff08;超出时间限制&#xff09; 递归参数与返回值 void reversal(int i, int k) 每次我们处理第i个台阶到第k个…

在vue3中用PlayCanvas构建3D物理模型

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 3D 物理引擎示例&#xff1a;PlayCanvas 创建可互动的物理场景 应用场景 本示例演示了如何使用 PlayCanvas 创建一个交互式的 3D 物理场景。在这个场景中&#xff0c;用户可以创建和删除椅子&#xff0c;椅子…

Python入门教程 - 模块、包(八)

目录 一、模块的概念 二、模块的导入方式 三、案例演示 3.1 import模块名 3.2 from 模块名 import 功能名 3.3 from 模块名 import * 3.4 as定义别名 四、自定义模块 4.1 制作自定义模块 4.2 测试模块 4.3 注意事项 4.4 __all__ 五、Python包 5.1 包的概念 5.2 …

操作系统——信号

将信号分为以上四个阶段 1.信号注册&#xff1a;是针对信号处理方式的规定&#xff0c;进程收到信号时有三种处理方式&#xff1a;默认动作&#xff0c;忽略&#xff0c;自定义动作。如果不是自定义动作&#xff0c;这一步可以忽略。这个步骤要使用到signal/sigaction接口 2.…

2002-2023年款别克君威 君威GS维修手册和电路图资料更新

经过整理&#xff0c;2002-2023年款别克君威 君威GS全系列已经更新至汽修帮手资料库内&#xff0c;覆盖市面上99%车型&#xff0c;包括维修手册、电路图、新车特征、车身钣金维修数据、全车拆装、扭力、发动机大修、发动机正时、保养、电路图、针脚定义、模块传感器、保险丝盒图…

Dorkish:一款针对OSINT和网络侦查任务的Chrome扩展

关于Dorkish Dorkish是一款功能强大的Chrome扩展工具&#xff0c;该工具可以为广大研究人员在执行OSINT和网络侦查任务期间提供强大帮助。 一般来说&#xff0c;广大研究人员在执行网络侦查或进行OSINT信息收集任务过程中&#xff0c;通常会使用到Google Dorking和Shodan&…