前端实现websocket(vue3)

在config/index.js文件中配置一下websocket

// websocket的域名和端口号的配置
const BASE_URL = 'localhost';
const WS_PORT = '8080';
const WS_ADDRESS = `ws://${BASE_URL}:${WS_PORT}`;

export const useWebsockt = (handleMessage) => {
  const ws = new WebSocket(WS_ADDRESS );

  //ws.binaryType = 'arraybuffer'; //可将 WebSocket 对象的 binaryType 属性设为“blob”或“arraybuffer”。默认格式为“blob”(您不必在发送时校正 binaryType 参数)。
  const init = () => {
    ws.addEventListener('open', handleOpen, false);
    ws.addEventListener('close', handleClose, false);
    ws.addEventListener('error', handleError, false);
    ws.addEventListener('message', handleMessage, false);
  };

  function handleOpen(e) {
    console.log('WebSocket open', e);
  }

  function handleClose(e) {
    console.log('WebSocket close', e);
  }

  function handleError(e) {
    console.log('WebSocket error', e);
  }

  init();
  return ws;
};

在页面中使用websocket
在这里插入图片描述
页面代码如下:

<template>
  <div class="body-content">
    <div class="web-socket">
      <header class="header">聊天室</header>
      <main class="main-content">
        <block v-for="(item, index) in msgListSelf" :key="index">
          <!-- business -->
          <div class="business" :key="index" v-if="!item.right">
            <div class="business-left">
              <img
                class="business-img header-img"
                src="https://qiyukf.com/sdk/res/skin/default/ico-kefu@2x.png?imageView&type=png%7CimageView&thumbnail=76y76&axis=5"
                alt=""
              />
              <ul>
                <p class="icon business-icon"></p>
                <p class="chat-content">{{ item.msg }}</p>
              </ul>
              <p class="curent-time">{{ getTime(item.curentTime) }}</p>
            </div>
          </div>

          <!-- user -->
          <div class="userSelf" :key="index + 10000" v-else>
            <div class="user-right">
              <ul>
                <p class="chat-content">{{ item.msg }}</p>
                <p class="icon userSelf-icon"></p>
              </ul>
              <img
                class="userSelf-img header-img"
                src="https://qiyukf.com/sdk/res/skin/default/ico-kefu@2x.png?imageView&type=png%7CimageView&thumbnail=76y76&axis=5"
                alt=""
              />
              <p class="curent-time">{{ getTime(item.curentTime) }}</p>
            </div>
          </div>
        </block>
      </main>

      <footer class="footer">
        <!-- contenteditable="true"标签可以编辑 -->
        <textarea
          contenteditable="true"
          @keydown="keydownCode"
          v-model="msg"
          placeholder="请您输入内容"
        ></textarea>
        <button @click="msgBtnClick">发送</button>
      </footer>
    </div>
  </div>
</template>

<script >
import { reactive, ref, toRefs } from "vue";
import { useWebsockt } from "../../hooks";
import { useRoute } from "vue-router";

export default {
  setup(params) {
    // 接收路由跳转携带参数
    const Route = useRoute();
    const username = Route.query.username;
    console.log("参数", username);

    const state = reactive({
      msg: "",
      msgListSelf: [],
    });

    // 接收到socket的实时数据
    const ws = useWebsockt(handleMessage);
    // 解析Blob进制流
    const reader = new FileReader();

    // 接收实时的数据
    function handleMessage(evt) {
      if (evt.data) {
        isBlob(evt.data);
      }
    }

    // 获取时间
    function getTime(time) {
      const oDate = new Date(time); //实例一个时间对象;
      const year = oDate.getFullYear(); //获取系统的年;
      const month = oDate.getMonth() + 1; //获取系统月份,由于月份是从0开始计算,所以要加1
      const date = oDate.getDate(); // 获取系统日,
      const h = oDate.getHours(); //获取系统时,
      const m = oDate.getMinutes(); //分
      const s = oDate.getSeconds(); //秒

      return `${year}年${month}月${date}日 ${h}:${m}`;
    }

    // 是否返回是二进制流 默认是Blob类型的
    const isBlob = (data) => {
      let result = null;
      if (data instanceof Blob) {
        reader.readAsText(data, "UTF-8");
        reader.onload = (e) => {
          result = JSON.parse(reader.result);

          // 判断当前是本人 本人的话统一将数据排到右侧
          if (username === result.username) {
            result.right = true;
          }
          state.msgListSelf.push(result);
          console.log(state.msgListSelf);
        };
      }
      return result;
    };

    // 回车发送消息
    const keydownCode = (e) => {
      if (e.keyCode === 13) {
        msgBtnClick();
      }
    };

    // 点击发送消息
    const msgBtnClick = () => {
      const _msg = state.msg;
      if (!_msg.trim().length) return;

      //发送信息
      ws.send(
        JSON.stringify({
          id: new Date().getTime(),
          msg: _msg,
          curentTime: new Date().getTime(),
          username,
        })
      );

      state.msg = "";
    };
    function binarystate(ev) {
      return JSON.parse(
        new TextDecoder("utf-8").decode(new Uint8Array(ev.state))
      );
    }

    return {
      ...toRefs(state),
      msgBtnClick,
      getTime,
      keydownCode,
    };
  },
};
</script>

<style scoped>
* {
  padding: 0;
  margin: 0;
}

.body-content {
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.web-socket {
  width: 500px;
  height: 500px;
  background: rgb(238, 235, 235);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  overflow: hidden;
  border: 1px solid #ccc;
}

.header {
  height: 40px;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #f96868;
  font-size: 16px;
  color: #fff;
}

.main-content {
  flex: 1;
  padding: 15px 10px;
  overflow-y: auto;
}

.footer {
  height: 100px;
  border-top: 1px solid #ccc;
  background-color: #fff;
  display: flex;
  flex-direction: column;
  padding: 3px 6px;
  /* box-sizing: border-box; */
}

.footer textarea {
  min-height: 50px;
  border: none;
  resize: none;
  cursor: pointer;
  padding: 3px;
  margin-bottom: 10px;
  /* 去掉聚焦默认边框 */
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  -webkit-user-modify: read-write-plaintext-only;
  outline: none;
  box-shadow: none;
}

.footer button {
  width: 70px;
  height: 26px;
  background: crimson;
  opacity: 0.7;
  color: #fff;
  font-size: 14px;
  align-self: flex-end;
  border: none;
  border-radius: 5px;
}

.business {
  display: flex;
  justify-content: flex-start;
  margin-bottom: 25px;
  position: relative;
}

.userSelf {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 25px;
  position: relative;
}

.user-right {
  width: auto;
  display: flex;
}

.business-left {
  width: auto;
  display: flex;
}

.header-img {
  width: 38px;
  height: 38px;
  border-radius: 19px;
  background: aquamarine;
}

.userSelf-img {
  background: #f96868;
}

.business-left ul {
  position: relative;
  left: 10px;
  /* display: flex; */
  align-self: center;
  max-width: 400px;
  border-radius: 10px;
}

.user-right ul {
  position: relative;
  right: 10px;
  max-width: 400px;
  align-self: center;
  background: #f96868;
  border-radius: 10px;
}

.business-left .icon {
  left: -6px;
  border-right-width: 6px;
  border-right-color: #fff;
}

.user-right .icon {
  right: -6px;
  border-left-width: 6px;
  border-left-color: #f96868;
}

.icon {
  position: absolute;
  top: 10px;
  width: 0;
  height: 0;
  border: 0 solid #fff;
  border-top: 7px solid transparent;
  border-bottom: 7px solid transparent;
}
.chat-content {
  background: #fff;
  border-radius: 10px;
  padding: 10px;
  max-width: 100%;
}

.user-right .chat-content {
  background: #f96868;
  color: #fff;
}
.business-left .curent-time {
  position: absolute;
  bottom: -22px;
  left: 48px;
  color: #000;
  opacity: 0.6;
  font-size: 12px;
}

.business-left .curent-username {
  left: 0;
  color: #000;
}
.curent-username {
  position: absolute;
  top: 43px;
  font-size: 12px;
  opacity: 0.6;
}

.user-right .curent-username {
  right: 0px;
  color: #f96868;
}

.user-right .curent-time {
  position: absolute;
  bottom: -22px;
  right: 48px;
  color: #f96868;
  opacity: 0.8;
  font-size: 12px;
}
</style>

以上逻辑涉及到是否是当前用户判断,好展示信息,从而加载不同样式布局,图暂时添加一个简单的用户名称窗口,这窗口填完之后跳转聊天窗。实际开发是根据登录用户信息来处理。
在这里插入图片描述

<template>
  <div class="chat-clients">
    <p class="chat-box">
      <input type="text" v-model="username" placeholder="请您输入聊天室昵称" />
      <button @click="entryChatClient">进入聊天室</button>
    </p>
  </div>
</template>

<script>
import { reactive, toRefs } from "vue";
import { useRouter } from "vue-router";

export default {
  setup() {
    const state = reactive({
      username: "",
    });

    const router = useRouter();

    const entryChatClient = () => {
      if (state.username) {
        router.push({
          path: "/websocket",
          query: { username: state.username },
        });
      }
    };
    return { entryChatClient, ...toRefs(state) };
  },
};
</script>

<style scoped>
.chat-clients {
  width: 100%;
  height: 100vh;
  background: #000;
  opacity: 0.8;
  display: flex;
  justify-content: center;
}

.chat-box {
  margin-top: 100px;
  width: 300px;
  height: 200px;
  border-radius: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
}

.chat-box input {
  height: 50px;
  width: 200px;
}

.chat-box button {
  height: 50px;
  flex: 1;
}
</style>

  • 2
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追逐梦想之路_随笔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值