关注
前端聊天怎么实现艾特@功能?高亮@信息,仿微信、qq艾特功能

以上篇文章的代码为基准,继续扩展实现艾特@功能,没看过的可以点击链接查看前端实现输入框表情输入及发送,仿b站、qq小表情输入

此demo使用vue3

实现效果图
显示的艾特@输入框
艾特@消息发送效果

声明:这只是个demo,不涉及与后端交互,不过会在该交互的地方标记,如需实际应用于项目,请根据实际情况进行改造完善!

艾特列表组件定义

<template>
  <!-- userGroupPosition父组件传递显示定位 -->
  <div class="user-group" :style="{ left: userGroupPosition.x, top: userGroupPosition.y }">
    <ul class="user-list">
      <li v-for="(user, index) in userGroup" :key="user.id" class="user-item" :class="{ active: activeIndex === index }" @click="selectAtUser(user)">
        <img :src="user.avatar" />
        <span>{{ user.name }}</span>
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref, defineProps, toRefs, onMounted, defineEmits } from "vue";
/**
 * @param userGroup 聊天群员列表
 * @param userGroupPosition 列表组件显示定位
 */
const props = defineProps(["userGroup", "userGroupPosition"]);
// 选择群员事件
const emit = defineEmits(["atUser"]);
const { userGroup, userGroupPosition } = toRefs(props);
// 此变量用来使用键盘上下按钮时,选择群员
let activeIndex = ref(-1);
// 给window挂载键盘事件,这样使用键盘上下键时可以选择群员
onMounted(() => {
  window.addEventListener("keydown", keyboardSelect);
});
// 键盘事件 通过修改activeIndex值,达到选择群员
function keyboardSelect(e) {
  if (e.key === "ArrowDown") {
    if (activeIndex.value !== userGroup.value.length - 1) {
      activeIndex.value++;
    } else {
      activeIndex.value = 0;
    }
  } else if (e.key === "ArrowUp") {
    if (activeIndex.value <= 0) {
      activeIndex.value = userGroup.value.length - 1;
    } else {
      activeIndex.value--;
    }
  } else if (e.key === "Enter") {
    selectAtUser(userGroup.value[activeIndex.value]);
  }
}
// 给父组件传递选择的群员
function selectAtUser(user) {
  emit("atUser", user);
}
</script>

<style scoped>
.user-group {
  position: fixed;
}
.user-list {
  list-style: none;
  padding: 0;
  border: 1px solid #e1e1e1;
  border-radius: 6px;
}

.user-item {
  height: 50px;
  line-height: 50px;
  padding: 0 20px;
  cursor: pointer;
  background-color: white;
  &:hover,
  &.active {
    background-color: #e1e1e1;
  }
  &:not(:last-of-type) {
    border-bottom: 1px solid #e1e1e1;
  }
  img {
    width: 30px;
    vertical-align: middle;
  }
}
</style>

父组件主逻辑处理

首先修改消息输入框,增加输入事件,用以判断显示输入框。且在上篇中发送消息采用了@keydown.enter.prevent绑定事件的形式发送,直接改成@keydown,用判断的方式发送,因为显示艾特输入框时,按回车是要选择群员的

<!-- 消息输入框 -->
      <div class="msg-input" ref="msgInput" contenteditable @blur="getAfterBlurIndex" @keydown="msgInputKeyDown" @input="msgInputFun"></div>
 <!-- 可艾特的聊天群员 -->
    <userGroup :userGroup="userGroupData" :userGroupPosition="userGroupPosition" v-if="showAtSelect" @at-user="selectAtUser"></userGroup>     

定义相关变量,群员列表、列表组件定位坐标、是否显示艾特组件

// 群员列表
const userGroupData = reactive([
  { name: "用户A", id: 1, avatar: require("@/assets/logo.png") },
  { name: "用户B", id: 2, avatar: require("@/assets/logo.png") },
  { name: "用户C", id: 3, avatar: require("@/assets/logo.png") },
]);
// 用户列表定位
const userGroupPosition = reactive({
  x: "0px",
  y: "0px",
});
let showAtSelect = ref(false);

定义输入事件,当输入@时,获取输入框光标相对页面坐标,并显示输入框组件。(失焦时,在失焦事件getAfterBlurIndex里,使用 setTimeout(() => {
showAtSelect.value = false;}, 100);隐藏艾特组件)

// 输入框输入事件
function msgInputFun(e) {
  if (e.data === "@") {
    setTimeout(() => {
      const { x, y } = getCursorPosition();
      showAtSelect.value = true;
      userGroupPosition.x = x + "px";
      userGroupPosition.y = y + "px";
    }, 100);
  } else {
    showAtSelect.value = false;
  }
}
// 获取输入框中的光标相对页面的坐标
function getCursorPosition() {
  let sel = window.getSelection();
  let range = document.createRange();
  range.selectNode(sel.focusNode);
  range.setStart(sel.focusNode, sel.focusOffset);
  const { x, y } = range.getBoundingClientRect();
  return { x, y };
}

这个时候可以显示出来群员选择列表了,再来定义键盘事件,在列表组件显示时阻止上下按钮的默认行为。如果不显示且按下回车时发送消息

// 显示艾特选择组件时,阻止输入框默认上下按键及回车行为
function msgInputKeyDown(e) {
  if (showAtSelect.value && ["ArrowUp", "ArrowDown", "Enter"].includes(e.key)) {
    return e.preventDefault();
  }
  if (e.key === "Enter") {
    e.preventDefault();
    sendMsg();
  }
}

然后编写选择群员事件,先获取并设置输入框选区,创建一个span标签节点,把被艾特人相关信息填入后插入到输入框子节点里。注意:创建的span标签里写了一个@,所以需要chatInputOffset.deleteContents()删除原@字符

function selectAtUser(user) {
  showAtSelect.value = false;
  msgInput.value.blur();
  getInputSelection();
  chatInputOffset.setStart(focusNode, focusOffset.value - 1);
  chatInputOffset.setEnd(focusNode, focusOffset.value);
  chatInputOffset.deleteContents();
  const atElement = `<span userId='${user.id}' userName='${user.name}' contentEditable="false" class="at-msg" style="color:#2E77E5">@${user.name}&nbsp;</span>`;
  chatInputOffset.collapse(false);
  const node = chatInputOffset.createContextualFragment(atElement);
  let c = node.lastChild;
  chatInputOffset.insertNode(node);
  if (c) {
    chatInputOffset.setEndAfter(c);
    chatInputOffset.setStartAfter(c);
  }
  let j = window.getSelection();
  j.removeAllRanges();
  j.addRange(chatInputOffset);
}

这个时候就可以把@消息发送出去了

let msgType = ref("text"); // 发送的消息类型

function sendMsg() {
  console.log("msgInput:", msgInput);
  chatMsgRecord.value = ""; // 先清空一下旧消息
  msgType.value = "text"; // 初始化消息类型
  msgInput.value.childNodes.forEach((element) => {
    // 如果是emoji表情图片的话,则转义
    if (element.nodeName === "IMG" && element.className === "emoji") {
      chatMsgRecord.value += element.alt;
    } else if (element.className === "at-msg") {
      chatMsgRecord.value += element.innerText + " ";
      msgType.value = "groupChatAt"; // 根据实际约束类型修改
    } else {
      chatMsgRecord.value += element.data;
    }
  });
  // 清空输入框中的内容
  msgInput.value.innerHTML = "";
  msgInput.value.innerText = "";
  // 在这里使用websocket把数据发送给后端
  // socketSend({text:chatMsgRecord,type:msgType})
}

如有更好的意见欢迎在评论区指出!

转载自CSDN-专业IT技术社区

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/zzz18789/article/details/136510930

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--