【你的第二个socket应用】Vue3+Node实现一对一即时聊天应用

3,002 阅读4分钟

Hi~,我是一碗周,如果写的文章有幸可以得到你的青睐,万分有幸~

🍑 写在前面

前一段时间通过WebSocket实现了一个即时通讯聊天室,使用是Vue3+Node,那篇文章点我进入,这篇文章在上一篇文章的基础上进行一个简单的扩展,实现一个一对一即时聊天应用。

注:这篇文章完全是在上一篇的基础上进行的,直接看的话可能会懵逼。

运行效果如下图所示:

私聊功能_EbvC0kK2Rq.gif

🍓 组件的编写

🍇 私聊组件的编写

这里完全是在上一篇文章的基础上进行的,这里只需要扩展一个抽屉组件就可以了,上图中私聊的组件是复用的群聊组件,实现代码如下:

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

interface Props {
  modelValue: boolean
}
const props = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const aniShow = ref(false)
const show = computed({
  get() {
    aniShow.value = props.modelValue
    return props.modelValue
  },
  set(v) {
    emitUpdate(v)
  },
})
const emitUpdate = (v: boolean, time = 300) => {
  aniShow.value = v
  window.setTimeout(() => {
    emit('update:modelValue', v)
  }, time)
}
</script>
<template>
  <div class="ywz-drawer h-screen w-screen fixed z-10 top-0" v-show="show">
    <div
      class="drawer-overlay absolute inset-0 z-0"
      @click="emitUpdate(false)"
    ></div>
    <Transition>
      <div
        v-show="aniShow"
        class="drawer-content absolute right-0 top-0 cursor-default h-screen bg-base-100"
      >
        <slot></slot>
      </div>
    </Transition>
  </div>
</template>
<style scoped>
.drawer-overlay {
  --tw-bg-opacity: 0.4;
  opacity: 0.999999;
  cursor: pointer;
  background-color: hsl(var(--nf, var(--n)) / var(--tw-bg-opacity));
  transition: all 0.3s;
}
.v-enter-active,
.v-leave-active {
  transition: all 0.3s;
}

.v-enter-from,
.v-leave-to {
  transform: translateX(100%);
}
</style>

这个组件我们通过v-model的方式实现抽屉的打开与关闭,这里还为组件的进入的退出增加了一个过渡效果。

🍅 NavHeader组件的修改

这里我们在NavHeader组件的头部展示了用户的头像,如果存在新消息(通过new属性来判断)则展示一个绿色的圆点,点击头像触发一个事件,告诉父组件可以进行操作,实现代码如下:

<script setup lang="ts">
import { computed } from 'vue'

interface Props {
  groupName: string
  personNumber: number
  userList: Map<any, any>
  curUserId: string
}
interface User {
  id: string
  avatar: string
  name: string
  new: boolean
}
const props = defineProps<Props>()
const emit = defineEmits(['more'])
const handleMore = (user: User) => {
  emit('more', user)
}
const users = computed<User[]>(() => {
  const list: User[] = []
  if (props.userList.size === 0) return []
  props.userList.forEach((value, key) => {
    if (key !== props.curUserId) {
      list.push({
        avatar: value.avatar,
        id: key,
        name: value.name,
        new: value.new,
      })
    }
  })
  return list
})
</script>

<template>
  <!-- 顶部栏 -->
  <div class="navbar text-primary-content rounded-box space-x-1 h-16">
    <div class="flex-1">
      <a class="normal-case text-xl pl-4"
        >{{ props.groupName }}({{ props.personNumber }})</a
      >
    </div>
    <div class="flex-none avatar-list pr-4">
      <div
        class="avatar ml-1 cursor-pointer"
        :class="item.new ? 'online' : ''"
        @click="handleMore(item)"
        v-for="item in users"
        :key="item.id"
      >
        <div class="w-6 rounded-full">
          <img :src="item.avatar" />
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped></style>

🍍 服务端的实现

服务端这里比较好实现,主要是监听消息的发送以及告诉客户端谁给谁发送了消息,实现代码如下:

socket.on('send-user', e => {
  const sendUserId = e.sendUserId
  socket.to(sendUserId).emit('message-user', e)
})
  • 首先我们通过监听send-user事件,监听用户什么时候发送私聊消息;

  • 发送消息后我们获取到发送给某个用户的id;

  • 最后我们将这个消息通过to的方式指定的发出一个message-user的事件,告诉他谁给他发的消息。

🍈 客户端的实现

现在我们就根据上面打下的基础,来编写客户端。

🍉 定义变量与基础方法

首先我们先定义变量以及数据结构,代码如下:

interface User {
  name: string
  avatar: string
  id: string
  new: boolean
}
// 控制控制的显示与隐藏
const drawerShow = ref(false)
// 与所有用户私聊的内容存储单元
const userChatData = ref<Map<string, ChatDataItem[]>>(new Map())
// 正在私聊的用户id
const chatUserId = ref('')
// 私聊聊天框的文字内容
const userMessage = ref('')
// 打开抽屉
const handleOpenDrawer = (user: typeof curUser) => {
  drawerShow.value = true
}
// 私聊发送消息
const handleSendUser = (v: string) => {
  const obj = {}
  // 发送消息
  socket.emit('send-user', obj)
}
// 监听接受消息
socket.on('message-user', (e: any) => {
  
})

这里我们通过****数据结构来存储用户的聊天记录,这里也可以使用普通对象存储,这里我为了后续方便扩展我使用了Map

🍒 实现私聊窗口的打开

实现私聊窗口打开非常的容易,实现代码如下:

const handleOpenDrawer = (user: typeof curUser) => {
  chatUserId.value = user.id
  // 清空头像右上角的绿色圆圈
  const u = userList.value.get(chatUserId.value)
  if (u) {
    u.new = false
  }
  drawerShow.value = true
}

🍐 实现私聊消息的发送

私聊发送消息几乎与群聊发送消息一致,不同的是需要传递接收消息那个人的id以及保存的位置也所有不同,实现代码如下:

const handleSendUser = (v: string) => {
  const obj = {
    id: Math.random().toString().split('.')[1].slice(0, 10),
    name: curUser.name,
    avatar: curUser.avatar,
    content: v,
    userId: curUser.id,
    sendUserId: chatUserId.value,
  }
  // 在 userChatData 中新增一条数据,表示自己发送的
  const type: 'me' = 'me'
  // 判断是否与该用户聊过天,如果没有创建一个空的聊天记录
  if (!userChatData.value.has(chatUserId.value)) {
    userChatData.value.set(chatUserId.value, [])
  }
  // 获取聊天记录,准备添加
  const _chatData = userChatData.value.get(chatUserId.value) ?? []
  _chatData.push(Object.assign({}, { type }, obj))
  // 清空 input box 中的内容
  userMessage.value = ''
  // 发出send事件,将消息发送出去
  socket.emit('send-user', obj)
}

🍏 实现消息的接收

接受消息也比较简单,首选确定是谁发送的消息,然后将其添加到指定用户的聊天记录中即可。

socket.on('message-user', (e: any) => {
  const msg = Object.assign({}, e, { type: 'your' }) as ChatDataItem
  const sendId = e.userId
  if (!userChatData.value.has(sendId)) {
    userChatData.value.set(sendId, [])
  }
  const chatData = userChatData.value.get(sendId) ?? []
  chatData.push(msg)
  // 设置小绿点
  const u = userList.value.get(sendId)
  if (u) {
    u.new = true
  }
})

🥭 写在最后

到这为止我们就实现了简单的私聊功能,由于该篇文章与上一篇文章【你的第一个socket应用】Vue3+Node实现一个WebSocket即时通讯聊天室,有非常强的联系,所有需要先阅读过前面的那一篇。

上面的代码还有好多的可以优化的点,比如在群聊中点击头像可以私聊等(这个功能我已经实现,可以在GitHub中找到参考)。

创作不易,如果可以三连支持一下,你的三连是我持续输出的动力😜😜

023B982F_CGmCSC7VVJ.gif