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>

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值