websocket-前端代码

原文链接:https://www.sinye.xyz/front-end/729.html

我纠结了很长时间要不要写这篇文章。因为,如果自己开发着玩,需要学的东西太多了,如果真正开发项目,肯定不是一个人能完成的,所以我写的这点皮毛根本不够用。
既然是我自己业余时间写这篇文章,那我就按照我开发这个'即时通讯'项目的流程简单描述一下。

1、页面先行

作为前端开发,没有页面怎么行,功能都是在静态页面的基础上添加的。

html:
<div class="wrapper">
    <div class="container">
        <div :class="'mask '+(user_list_bool?'active':'')" @click="user_list_bool=false"></div>
        <div :class="'left '+(user_list_bool?'active':'')">
            <div class="top">
                <div class="">在线人数:<span id="numbers">{{userLength}}</span> 人
                </div>
            </div>
            <ul class="people">
              <li class="person flex_c_b" data-chat="person1" v-for="(item,index) in user_list" :key="index">
                <div class="flex_c_s">
                  <img :src="item.headerimg" alt=""/>
                  <div>
                    <span class="name">{{item.username}}</span>
                    <span class="preview">在线</span>
                  </div>
                </div>
                <span class="time">{{item.login_time}}</span>
              </li>
            </ul>
        </div>
        <div class="right">
            <div class="top flex_c_b">
                <span>群名: <span class="name">又甘又刻</span></span>
                <div class="chakang" @click="user_list_bool=true"><div class="numbers">{{userLength}}</div>在线</div>
            </div>
            <!-- <van-pull-refresh v-model="isLoading" @refresh="onRefresh"> -->
            <div class="mesbox" ref="mesbox">
              <div class="chat active-chat" data-chat="person1">
                <van-loading type="spinner" size="18px" v-show="isLoading"/>
                <div v-for="(item,index) in mes" :key="index" class="mg_t20">
                  <div v-if="item.type=='sys'">
                    <div class="conversation-start">
                      <span>{{item.msg}}</span>
                    </div>
                  </div>
                  <div v-else>
                    <div class="message">
                      <img :class="item.youMe=='me'?'me-header':''" :src="item.head" alt=""/>
                      <div :class="'bubble '+item.youMe" v-html="item.msg"></div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
            <div class="write">
                <a href="javascript:;" class="write-link attach"></a>
                <input type="text" id="input-value" @keyup.enter="confirm()" v-model="inputValue"/>
                <a href="javascript:;" class="write-link smiley"></a>
                <a href="javascript:;" class="write-link send" @click="send()"></a>
            </div>
        </div>
    </div>
  </div>
css:
*, *:before, *:after {box-sizing: border-box;}html{height: 100%;}:root {--white: #fff;--black: #000;--bg: #f8f8f8;--grey: #999;--dark: #1a1a1a;--light: #e6e6e6;--wrapper: 100vw;--blue: #00b0ff;}body {background-color: var(--bg);-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-rendering: optimizeLegibility;font-family: 'Source Sans Pro', sans-serif;font-weight: 400;font-size: .16rem;background-image: url("../img/image.jpg");background-size: cover;background-repeat: none;height: 100%;}.wrapper {position: relative;width: var(--wrapper);height: 100%;}.container {position: relative;width: 100%;height: 100%;background-color: var(--white);}.container .left {position: absolute;left: -100%;width: 60%;max-width: 250px;height: 100%;border: 1px solid var(--light);background-color: var(--white);z-index: 3;-webkit-transition: ease .4s;-moz-transition: ease .4s;-o-transition: ease .4s;transition: ease .4s;}.container .left.active{left: 0%;}.mask{position: absolute;width: 100vw;height: 100%;left: 0;top: 0;z-index: 2;background-color: rgba(0, 0, 0, 0.3);display: none;}.mask.active{display: block;}.container .left .top {position: relative;width: 100%;height: 47px;padding: .29rem;font-size: 14px;}.container .left .top #numbers{font-size: 16px;}.container .left .top:after {position: absolute;bottom: 0;left: 50%;display: block;width: 90%;height: 1px;content: '';background-color: var(--light);-webkit-transform: translate(-50%, 0);transform: translate(-50%, 0);}.container .left input {float: left;width: 1.88rem;height: .42rem;padding: 0 .15rem;border: 1px solid var(--light);background-color: #eceff1;border-radius: .21rem;font-family: 'Source Sans Pro', sans-serif;font-weight: 400;}.container .left input:focus {outline: none;}.container .left a.search {display: block;float: left;width: .42rem;height: .42rem;margin-left: .1rem;border: 1px solid var(--light);background-color: var(--blue);background-image: url("../img//name-type.png");background-repeat: no-repeat;background-position: top .12rem left .14rem;border-radius: 50%;}.container .left .people {margin-left: -1px;border-right: 1px solid var(--light);border-left: 1px solid var(--light);width: calc(100% + .02rem);}.container .left .people .person {position: relative;width: 100%;padding: 10px 14px;cursor: pointer;background-color: var(--white);}.container .left .people .person:after {position: absolute;bottom: 0;left: 50%;display: block;width: 86%;height: 1px;content: '';background-color: var(--light);-webkit-transform: translate(-50%, 0);transform: translate(-50%, 0);}.container .left .people .person img {width: 40px;height: 40px;border-radius: 50%;border: 1px solid #eee;margin-right: 5px;}.container .left .people .person .name {font-size: 14px;line-height: .22rem;color: var(--dark);font-family: 'Source Sans Pro', sans-serif;font-weight: 600;}.container .left .people .person .time {font-size: 12px;color: var(--grey);background-color: var(--white);}.container .left .people .person .preview {font-size: 12px;color: green;display: block;margin-top: 3px;text-align: start;}.container .left .people .person.active, .container .left .people .person:hover {margin-top: -1px;margin-left: -1px;padding-top: .13rem;border: 0;background-color: var(--blue);width: calc(100% + .02rem);padding-left: calc(10% + 1px);}.container .left .people .person.active span, .container .left .people .person:hover span {color: var(--white);background: transparent;}.container .left .people .person.active:after, .container .left .people .person:hover:after {display: none;}.container .right {position: relative;float: left;width: 100%;height: 100%;}.container .right .top {width: 100%;height: 47px;padding: .15rem .29rem;background-color: #eceff1;font-size: 14px;}.container .right .top .chakang{display: flex;align-items: center;color: var(--blue);cursor: pointer;}.container .right .top span {color: var(--grey);}.container .right .top span .name {color: var(--dark);font-family: 'Source Sans Pro', sans-serif;font-weight: 600;}.container .right .mesbox{padding: 0 15px;height: calc(100% - 89px);overflow: auto;}.container .right .chat {position: relative;display: none;flex-direction: column;}.container .right .chat.active-chat {display: flex;text-align: left;}.container .right .chat .mg_t20:last-child{margin-bottom: 15px;}.container .right .write {position: absolute;bottom: 0;height: 42px;padding-left: 8px;border: 1px solid var(--light);background-color: #e8ebee;width: 100%;display: flex;align-items: center;padding: 0 10px;}.container .right .write input {font-size: 16px;width: 100%;height: 30px;margin: 5px 10px 5px 5px;padding: 0 10px;color: var(--dark);border: 0;outline: none;background-color: #f6f7f8;font-family: 'Source Sans Pro', sans-serif;font-weight: 400;border-radius: 4px;}.container .right .write .write-link.attach:before {display: inline-block;width: 20px;height: 42px;content: '';background-image: url("../img/attachment.png");background-repeat: no-repeat;background-position: center;}.container .right .write .write-link.smiley:before {display: flex;width: 20px;height: 42px;content: '';background-image: url("../img/smiley.png");background-repeat: no-repeat;background-position: center;}.container .right .write .write-link.send:before {display: flex;float: left;width: 20px;height: 42px;margin-left: 11px;content: '';background-image: url("../img/send.png");background-repeat: no-repeat;background-position: center;}.container .right .bubble {font-size: 16px;position: relative;display: inline-block;clear: both;padding: 10px 14px;vertical-align: top;border-radius: 5px;word-break: break-all }.container .right .bubble:before {position: absolute;top: 14px;display: block;width: 12px;height: 12px;content: '\00a0';-webkit-transform: rotate(29deg) skew(-35deg);transform: rotate(29deg) skew(-35deg);}.container .right .bubble.you {float: left;color: var(--dark);background-color: #eceff1;align-self: flex-start;-webkit-animation-name: slideFromLeft;animation-name: slideFromLeft;}.container .right .bubble.you:before {left: -4px;background-color: #eceff1;}.container .right .bubble.me {float: right;color: var(--white);background-color: var(--blue);align-self: flex-end;-webkit-animation-name: slideFromRight;animation-name: slideFromRight;}.container .right .bubble.me:before {right: -4px;background-color: var(--blue);}.container .right .conversation-start {position: relative;width: 100%;text-align: center;}.container .right .conversation-start span {font-size: 12px;display: inline-block;color: var(--grey);}.container .right .conversation-start span:before, .container .right .conversation-start span:after {position: absolute;top: 7px;display: inline-block;width: 20%;height: 1px;content: '';background-color: var(--light);}.container .right .conversation-start span:before {left: 0;}.container .right .conversation-start span:after {right: 0;}@keyframes slideFromLeft {0% {margin-left: -2rem;opacity: 0;}100% {margin-left: 0;opacity: 1;}}@-webkit-keyframes slideFromLeft {0% {margin-left: -2rem;opacity: 0;}100% {margin-left: 0;opacity: 1;}}@keyframes slideFromRight {0% {margin-right: -2rem;opacity: 0;}100% {margin-right: 0;opacity: 1;}}@-webkit-keyframes slideFromRight {0% {margin-right: -2rem;opacity: 0;}100% {margin-right: 0;opacity: 1;}}.message img {float: left;width: 40px;height: 40px;margin-right: 12px;border-radius: 50%;border: 1px solid #eee;}.you {margin-left: 60px;margin-top: -39px;}.me-header {float: right !important;margin-right: 0 !important;}.me {margin-right: 60px;margin-top: -39px;}.active-chat::-webkit-scrollbar, .left::-webkit-scrollbar {width: 2px;}

<span style="color: red">css里加载了一些图片,自己找几张图片替换了</span>

2、请慢用js脱发剂

我先简单的说一下我的逻辑:由于没有登入这一步操作,就不能确认是谁发的消息,所以我就用ip当作用户名(虽然ip会变,当短时间不变,对短时间测试没有影响)。然后前端需要操作的数据就是区分信息是在左侧还是右侧显示以及历史信息回显,就是用v-if判断ip显示不同html就行了。

websocket关键代码
connect() {
    // 创建一个 websocket 连接  ws://ip:端口号
    this.ws = new WebSocket("ws://websockets.sinye.xyz/websocket");

    // 连接状态 1已建立连接
    // console.log(this.ws.readyState)

    //  连接建立时触发
    this.ws.onopen = ()=>{
        this.onopen();
    };

    // 客户端接收服务端数据时触发
    this.ws.onmessage = (event)=>{
        this.onmessage(event)
    };

    // 连接关闭时触发
    this.ws.onclose = ()=>{
        this.onclose();
    };

    //  通信发生错误时触发
    this.ws.onerror = ()=>{
        this.onerror();
    };
},
// 通信建立成功
onopen(){
    var data = "系统消息:建立连接成功";
    console.log(data);
},
// 接收客户端的数据,发送数据
onmessage(e){
    var data = JSON.parse(e.data);
    // console.log(data)

    switch (data.type) {
        case 'handShake':
            //首次登录,发送登陆数据
            var user_info = {'type': 'login', 'msg': this.uname, 'headerimg': this.headerimg};
            this.sendMsg(user_info);
            break;
        case 'login':
            this.userList(data.user_list);
            this.systemMessage('系统消息: ' + data.msg + ' 已上线');
            break;
        case 'logout':
            this.userList(data.user_list);
            if (data.msg.length > 0) {
                this.systemMessage('系统消息: ' + data.msg + ' 已下线');
            }
            break;
        case 'user':
            this.messageList1(data);
            break;
        case 'system':
            this.systemMessage();
            break;
    }
},
// 关闭连接时触发
onclose(){
    console.log("连接关闭,定时重连");
    this.connect();
},
// websocket 错误事件
onerror(){
    var data = "系统消息 : 出错了,请退出重试.";
    console.log(data);
},
// 输入框输入后按回车,向服务器发送信息
confirm() {
    this.send();
},
// 发送的数据先保存到数据库
send() {
  let msg = this.inputValue.replace(/^[\s+]$/g, ' ')
  var data = {type: "user", msg: msg, id: this.userid}
  if(msg!=''){
    this.$axios.post(
      '/api/InfoSave/save', data
    ).then((response)=>{
      if(response.data !== ''){
        // 保存成功后再通过websocket群发
        this.sendMsg(data);
        this.inputValue = "";
      }
    })
  }
},
// 群发数据
sendMsg(msg) {
    var data = JSON.stringify(msg);
    this.ws.send(data);
},

发送和接收就已经完成了,接下来就是回显历史信息

历史信息回显
// 追加数据 上下线的系统消息
systemMessage(msg) {
    this.mes.push({type:'sys',msg:msg})
    this.active_chat.scrollTop = this.active_chat.scrollHeight;
},
// 追加从服务端返回的数据 左侧在线人数列表
userList(user) {
  this.user_list = []
  for (var i = 0; i < user.length; i++) {
    this.user_list.push({
      headerimg: user[i].headerimg,
      username: user[i].username,
      login_time: user[i].login_time,
    })
  }
  this.userLength = user.length
},
// 右侧聊天记录列表
messageList1(data) {
  // var html
    // 判读是不是自己发送的消息,对应的样式不同
    if (data.ip == this.uname) {   
      // 如果当前用户名和feom的用户名相同,就说明时自己发送的消息
      this.mes.push({type:'info', youMe:'me', msg:data.msg, head: data.headerimg})  
    } else {
      // 别人发送的信息列表
      this.mes.push({type:'info', youMe:'you', msg:data.msg, head: data.headerimg})
    }
    this.$nextTick(() =>{
      this.active_chat.scrollTop = this.active_chat.scrollHeight;
    })
},
// 右侧聊天记录列表
messageList2(data,bool) {
  // var html
    // 判读是不是自己发送的消息,对应的样式不同
    if (data.ip == this.uname) {   
      this.mes.splice(0,0,{type:'info', youMe:'me', msg:data.msg, head: data.headerimg}) 
    } else {
      this.mes.splice(0,0,{type:'info', youMe:'you', msg:data.msg, head: data.headerimg})
    }
    if(bool){
      this.$nextTick(() =>{
        this.active_chat.scrollTop = this.active_chat.scrollHeight;
      })
    }
    else{
      this.$nextTick(() =>{
        this.active_chat.scrollTop = this.active_chat.scrollHeight - this.oldH
      })
      
    }
},
// 获取历史信息ajax
onRefresh(bool) {
  this.isLoading = true;
  this.page++;
  this.$axios.post(
    '/api/InfoSave/get', {page: this.page}
  ).then((response)=>{
    if(response.data){
      for (let i = 0; i < response.data.length; i++) {
        const data = response.data[i];
        this.messageList2(data,bool)
      }
      this.isLoading = false;
    }else{
      this.page--
      this.isLoading = false;
    }
  })
},
// 监听滚动
handleScroll () {
  if (this.active_chat.scrollTop <= 0) {
    this.oldH = JSON.parse(JSON.stringify(this.active_chat.scrollHeight))
    this.onRefresh(false)
  }
},
// 获取ip并连接服务器
login(){
    this.$axios.get(
    '/api/login'
    ).then((response)=>{
    var data = response.data
    this.uname = data.ip
    this.headerimg = data.headerimg
    this.userid = data.id
    if(this.uname!=''){
        this.connect()
        this.onRefresh(true)
    }else{
        console.log('连接失败!')
    }
    })
},
初始化还是必须的
mounted(){
    this.login()
    this.$nextTick(() =>{
        this.active_chat = document.querySelector('.mesbox')
        this.$refs.mesbox.addEventListener('scroll', this.handleScroll)
    })
},
data() {
    return {
      userid: '',//用户id
      ws: {},
      uname: '',//用户名
      headerimg: '',//用户头像
      mes:[],//消息
      inputValue: '',//输入的信息
      active_chat: '',//信息dome
      user_list: [],//在线用户列表
      user_list_bool: false,//在线用户列表弹窗是否显示
      userLength: '',//在线人数
      page: 0,
      isLoading: false,
      oldH: '',//原来信息高度
    }
},

大致完成

成品链接:http://socket.sinye.xyz

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容