zoukankan      html  css  js  c++  java
  • 【珍惜时间】vue-websocket

    这个项目可能是个有始无终的项目?跟我一起分析吧,比较简单的一个项目
    另外,我也想跟自己说,我好像失去了那个努力的自己了。要珍惜时间,好好加油啊~
    项目地址为:https://github.com/xiaobeila/vue-websocket.git
    这个项目和其他的项目的区别是,这个项目里面将服务器端,即websocket.io直接与前端项目集成在一起了。

    //app.js
    var app = require('express')()
    var http = require('http').Server(app)
    var io = require('socket.io')(http)
    
    // 设置跨域访问
    app.all('*', function (req, res, next) {
      res.header('Access-Control-Allow-Origin', '*')
      res.header('Access-Control-Allow-Headers', 'X-Requested-With')
      res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS')
      res.header('X-Powered-By', ' 3.2.1')
      res.header('Content-Type', 'application/json;charset=utf-8')
      next()
    })
    
    /**
     * 路由配置
     */
    // 服务器根目录
    app.get('/', function (req, res) {
      res.send('<h1>Welcome Realtime Server</h1>')
    })
    // demo子目录
    app.get('/demo', function (req, res) {
      res.send('<h1>Welcome Realtime Server - demo</h1>')
    })
    
    // 在线用户
    var onlineUsers = []
    // 当前在线人数
    var onlineCount = 0
    
    /**
     * 建立socket链接
     */
    io.on('connection', function (socket) {
      console.log('a user connected')
    
      /**
         * 监听新用户加入
         */
      socket.on('login', function (obj) {
        // 将新加入用户的唯一标识当作socket的名称,后面退出的时候会用到
        socket.name = obj.userId
        // 检查在线列表,如果不在里面就加入
        if (!onlineUsers.hasOwnProperty(obj)) {
          onlineUsers.push(obj)
          onlineCount++// 在线人数+1
        }
        // 向所有客户端广播用户加入
        io.emit('login', {
          onlineUsers: onlineUsers,
          onlineCount: onlineCount,
          user: obj
        })
        console.log(socket.handshake)// 打印握手信息
        console.log(obj.userName + ' 登录')
      })
    
      /**
         * 监听用户退出
         */
      socket.on('disconnect', function () {
        console.log('[Leo]socket name => ', socket.name)
        // 将退出的用户从在线列表中删除
        for (let i = 0, len = onlineUsers.length; i < len; i++) {
          let user = onlineUsers[i]
          if (user.userId == socket.name) {
            let tempUser = user
            onlineUsers.splice(i, 1)
            onlineCount--
            io.emit('logout', {
              onlineUsers,
              onlineCount,
              user: tempUser
            })
            console.log(user.userName + ' 退出登录', JSON.stringify(tempUser))
            break
          }
        }
        console.log('剩余在线用户 => ', JSON.stringify(onlineUsers))
      })
    
      /**
         * 监听用户发布聊天内容
         */
      socket.on('message', function (obj) {
        // obj数据结构例子
    
        /* eslint-disable */
        let testObj = {
          'from': {
            'userId': '123',
            'userName': '123'
          },
          'to': {
            'userId': '456',
            'userName': '456'
          },
          content: '聊天内容',
          sendtime: '2016年10月9日 11:25:05'
        }
        // 向所有客户端广播发布的消息
        // io.emit('message', obj);
        io.emit(obj.to.userId, obj)
        console.log(
          obj.from.userName + ' 对 ' +
                obj.to.userName + ' 说 ' +
                obj.content
        )
      })
    })
    
    http.listen(3000, function () {
      console.log('listening on *:3000')
    })
    

    接下来我们看客户端的代码

    //main.js
    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    import store from './store'
    import * as filters from './filters'
    
    import VueTimeago from 'vue-timeago'
    // VueTimeago组件时间还有i18n的功能
    Vue.use(VueTimeago, {
      name: 'timeago', // component name, `timeago` by default
      autoUpdate: 1,
      maxTime: 86400,
      locale: 'zh-CN',
      locales: {
        'zh-CN': require('date-fns/locale/zh_cn'),
        'ja': require('date-fns/locale/ja')
      }
    })
    
    Object.keys(filters).forEach(key => {
      Vue.filter(key, filters[key])
    })
    
    Vue.config.productionTip = false
    
    const app = new Vue({
      router,
      store,
      ...App // Object spread copying everything from App.vue : render: h => h(App)
    }).$mount('#app')// 挂载到DOM元素
    
    export { app, store, router }
    
    // new Vue({
    //   render: h => h(App)
    // }).$mount('#app')
    
    

    router.js为

    import Vue from 'vue'
    import Router from 'vue-router'
    
    Vue.use(Router)
    
    export const asyncRouterMap = [
      {
        path: '*',
        redirect: '/login'
      },
      {
        path: '/',
        redirect: '/login',
        component: resolve => require(['./views/pages/login'], resolve)
      },
      {
        path: '/login',
        name: 'login',
        component: resolve => require(['./views/pages/login'], resolve)
      },
      {
        path: '/dashboard',
        name: 'dashboard',
        component: resolve => require(['./views/pages/dashboard'], resolve),
        children: [{
          path: '/chat/:id/:name',
          name: 'chat',
          component: resolve => require(['./views/pages/chat'], resolve)
        }]
      }
    ]
    
    export default new Router({
      mode: 'history',
      routes: asyncRouterMap
    })
    

    App.vue为

    <template>
      <div id="app">
        <router-view></router-view>
      </div>
    </template>
    
    <script>
    export default {
      name: 'app'
    }
    </script>
    
    <style>
    #app {
       100vw;
      height: 100vh;
    }
    </style>
    

    <template>
      <div id="login">
        <ul class="login">
          <li><input type="text" name="userName" id="userName" placeholder="请输入用户名" required autofocus v-model="userName" @keyup.13="doLogin" /></li>
          <li>
            <a href="javascript:void(0);" @click="doLogin" class="login-btn">登录</a>
          </li>
        </ul>
      </div>
    </template>
    <script>
    import { mapState, mapMutations } from 'vuex'
    import * as types from '../../store/mutation-types'
    
    import io from 'socket.io-client'
    import common from '../../utils/common'
    
    export default {
      name: 'login',
      data () {
        return {
          userName: '',
          password: ''
        }
      },
      computed: {
        ...mapState({
          me: ({ users }) => users.me,
          online: ({ users }) => users.online,
          socket: ({ base }) => base.socket
        })
      },
      methods: {
        ...mapMutations({
          login: types.LOGIN,
          genUid: types.GEN_UID,
          setSocket: types.SET_SOCKET
        }),
        doLogin () {
          const _self = this
          if (!this.userName) {
            console.log('请输入用户名')
            return
          }
    
          // TODO:ajax获取登录数据
          let user = {
            userId: common.genUid(),
            userName: _self.userName
          }
    
          // 连接websocket后端服务器
          _self.setSocket(io('ws://127.0.0.1:3000'))
    
          if (_self.socket) {
            // 告诉服务器端有用户登录
            _self.socket.emit('login', user)
    
            // 贮存登录用户的信息
            _self.login(user)
          }
    
          // 进入首页
          this.$router.push({ path: '/dashboard' })
        }
      }
    }
    </script>
    <style scoped>
    ul,
    li {
      list-style: none;
    }
    
    .login {
      position: absolute;
      top: 50%;
      left: 50%;
      text-align: center;
       400px;
      margin-left: -200px;
      margin-top: -150px;
      padding: 50px 20px;
      border-radius: 5px;
      box-shadow: 1px 1px 2px #ccc, -1px -1px 2px #ccc;
      background-color: #ffffff;
    }
    
    input[type="text"] {
      border: 1px solid #cccccc;
      line-height: 50px;
       100%;
      text-align: center;
    }
    
    .login-btn {
      display: inline-block;
      margin-top: 20px;
       100%;
      background-color: dodgerblue;
      color: #ffffff;
      line-height: 50px;
      text-decoration: none;
    }
    </style>
    

    接下来进入了dashboard页面

    <template>
      <div class="main">
        <div class="top-menu clearfix">
          <span>IM</span>
          <span>
            <span v-text="me.userName"></span>&nbsp;&nbsp;|&nbsp;&nbsp;
            <a href="javascript:;" @click="doLogout">退出</a>
          </span>
        </div>
        <ul class="user-list">
          <li v-for="item in online.users" :key="item.id" track-by="$index" @click='chat(item)' :class="{'v-link-active':item.userId==currentActive}">
            {{item.userName}}
            <span class="noread" v-if="item.noRead">{{item.noRead}}</span>
          </li>
        </ul>
        <div class="doc">
          <router-view keep-alive></router-view>
        </div>
      </div>
    </template>
    
    <script>
    import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
    import * as types from '../../store/mutation-types'
    
    export default {
      name: 'index',
      data () {
        return {
          currentActive: '-1'
        }
      },
      computed: {
        ...mapState({
          me: state => state.users.me,
          online: state => state.users.online,
          socket: ({ base }) => base.socket
        }),
        ...mapGetters({})
      },
      methods: {
        ...mapActions([]),
        ...mapMutations({
          logout: types.LOGOUT,
          updateUsers: types.UPDATE_USERS,
          addUsers: types.ADD_USERS,
          removeUser: types.REMOVE_USER,
          addReceiveMsg: types.ADD_RECEIVE_MSG
        }),
        doLogout () {
          this.socket.disconnect()
          this.logout()
          this.$router.push({ path: '/login' })
        },
        // 监听新用户登录
        listenLogin () {
          const _self = this
          if (_self.socket) {
            _self.socket.on('login', function (o) {
              console.log('[Leo]新用户加入 => ', o.user)
              console.log('[Leo]当前在线用户 => ', o.onlineUsers)
              _self.updateUsers(o.onlineUsers)
            })
          }
        },
        // 监听用户退出
        listenLogout () {
          const _self = this
          if (_self.socket) {
            _self.socket.on('logout', function (o) {
              console.log('[Leo]有用户退出 => ', o)
              _self.removeUser(o.user.userId)
            })
          }
        },
        // 监听消息发送
        listenMsg () {
          const _self = this
          if (_self.socket) {
            _self.socket.on(_self.me.userId, function (obj) {
              console.log('[Leo]有人对我说话 => ', obj.from.userName + ' 对 ' + obj.to.userName + ' 说 ' + obj.content)
              _self.addReceiveMsg(obj)
            })
          }
        },
        chat (user) {
          this.currentActive = user.userId
          this.$router.push({
            name: 'chat',
            params: {
              id: user.userId,
              name: user.userName
            }
          })
        }
      },
      created () {
        if (!this.me.userName) {
          this.$router.push({ name: 'login' })
        }
        this.listenLogin()
        this.listenLogout()
        this.listenMsg()
      }
    }
    </script>
    <style lang="less" scoped>
    .main {
      position: relative;
       100vw;
      height: 100vh;
      border: 1px solid #efefef;
      box-shadow: 1px 1px 15px #ccc;
      background-color: #efeff4;
      overflow: hidden;
    }
    
    .top-menu {
      background-color: #3d3d3d;
      color: #fff;
      height: 45px;
       100%;
      font-size: 12px;
      line-height: 45px;
      font-size: larger;
      font-family: "Microsoft YaHei UI", "微软雅黑", "Helvetica Neue", Helvetica,
        STHeiTi, sans-serif;
    
      span:first-child {
        text-align: left;
        margin-left: 10px;
    
        & + span {
          float: right;
          margin-right: 10px;
        }
      }
    
      a {
        color: #ffffff;
        text-decoration: none;
      }
    }
    
    ul,
    li {
      list-style: none;
      padding: 0;
      margin: 0;
    }
    
    .user-list {
      position: absolute;
      top: 45px;
      bottom: 0;
      left: 0;
      z-index: 9999999;
       300px;
      overflow-y: auto;
      background-color: #fff;
      box-shadow: 3px 2px 5px #ccc;
      @height: 30 px;
      li {
        padding: 10px;
        line-height: @height;
        cursor: pointer;
        border-bottom: 1px dashed #efefef;
    
        img {
          float: left;
           @height;
          border-radius: 50%;
        }
        & :hover,
        & :active {
          background: #efefef;
        }
        .noread {
          display: inline-block;
          background-color: #f00;
          color: #fff;
          min- 20px;
          height: 20px;
          border-radius: 50%;
          font-size: 12px;
          line-height: 20px;
          text-align: center;
        }
      }
    }
    .doc {
      position: absolute;
      top: 45px;
      bottom: 0;
      left: 300px;
      right: 0;
    }
    .v-link-active {
      background-color: #efefef;
    }
    </style>
    
    //srcviewspageschat.vue
    <template>
      <div class="chat">
        <div class="list">
          <ul>
            <li v-for="msg in getMsgs" :key="msg.id">
              <msg-item :type="msg.from.userId==me.userId?'me':'other'" :msg="msg"></msg-item>
            </li>
          </ul>
        </div>
        <div class="send">
          <div class="send-bar">
            <input type="file" id="fileImg" name="fileImg" style="display: none;" accept="image/*" ref="fileImg" @change="sendImg">
            <label for="fileImg" class="fa fa-picture-o" aria-hidden="true"></label>
          </div>
          <div class="send-msg">
            <textarea class="send-msg-input" placeholder="请输入聊天内容" autofocus v-model="content" @keyup.13="sendText" ref="msgInput"></textarea>
            <a href="javascript:void(0)" class="send-msg-btn" @click="sendText">发送</a>
          </div>
        </div>
      </div>
    </template>
    <script>
    import { mapState, mapMutations } from 'vuex'
    import * as types from '../../store/mutation-types'
    
    import msgItem from '@/components/msg-item'
    
    export default {
      name: 'chat',
      components: { msgItem },
      data () {
        return {
          content: '',
          fileImg: null
        }
      },
      computed: {
        ...mapState({
          me: ({ users }) => users.me,
          users: ({ users }) => users.online.users,
          socket: ({ base }) => base.socket
        }),
        getMsgs () {
          const _self = this
          let msgs = []
          for (let user of _self.users) {
            if (user.userId != _self.$route.params.id) continue
            if (user.msg) msgs = user.msg
            user.noRead = 0
            break
          }
    
          /* eslint-disable */
          setTimeout(_self.scrollToBottom, 0)
          console.log('[Leo]getMsgs => ', msgs)
          return msgs
        }
      },
      methods: {
        ...mapMutations({
          addSendMsg: types.ADD_SEND_MSG
        }),
        // 让浏览器滚动条保持在最低部
        scrollToBottom: function () {
          window.scrollTo(0, document.querySelectorAll('.list ul')[0].clientHeight)
          window.document.querySelectorAll('.list')[0].scrollTop = document.querySelectorAll('.list ul')[0].clientHeight
        },
        // 上传图片 <https://segmentfault.com/a/1190000004924160>
        sendImg (event) {
          let _vm = this
          let file = event.target.files[0] // 获取图片资源
          // 只选择图片文件
          if (!file.type.match('image.*')) {
            return false
          }
          let reader = new FileReader()
          reader.readAsDataURL(file)// 读取文件
          // 渲染文件
          reader.onload = function (arg) {
            _vm.submit('img', arg.target.result)
    
            _vm.$refs.fileImg.files[0] = null
            _vm.$refs.msgInput.focus()
          }
    
          // TODO:上传图片
          _vm.uploadFile(file).then(res => {
            console.log('[Leo]图片上传成功 => ', res)
          }).catch(error => {
            console.error('[Leo]图片上传出错 => ', error)
          })
        },
        // 提交聊天消息内容
        sendText () {
          const _vm = this
          if (_vm.content != '') {
            _vm.submit('text', _vm.content)
          } else {
            console.log('请输入聊天内容')
          }
          _vm.$nextTick(function () {
            _vm.scrollToBottom()
            _vm.content = ''
            _vm.$refs.msgInput.focus()
          })
          return false
        },
        // 提交聊天消息内容
        submit (type, content) {
          const _vm = this
          let obj = {
            'from': {
              'userId': _vm.me.userId,
              'userName': _vm.me.userName
            },
            'to': {
              'userId': _vm.$route.params.id,
              'userName': _vm.$route.params.name
            },
            'msgType': type,
            'content': content,
            'sendtime': (new Date()).getTime()
          }
          _vm.addSendMsg(obj)
          _vm.socket.emit('message', obj)
        },
        /**
         * 上传文件
         * @param file
         */
        uploadFile (file) {
          let formData = new FormData()
          // 把上传的数据放入form_data
          formData.append('img', file)
          // 异步提交数据
          return fetch('url', {
            method: 'POST',
            body: formData
          })
        }
      },
      mounted () {
        const _self = this
        _self.$nextTick(function () {
          _self.scrollToBottom()
          _self.$refs.msgInput.focus()
        })
      }
    }
    </script>
    <style scoped lang="scss" rel="stylesheet/scss">
    input,
    button,
    select,
    textarea {
      outline: none;
    }
    
    ul,
    li {
      list-style: none;
    }
    
    .chat {
      position: absolute;
      top: 0;
      bottom: 0;
      right: 0;
      left: 0;
      box-sizing: border-box;
      overflow: hidden;
    }
    
    .list {
      padding: 10px;
      height: calc(100% - 100px - 40px);
      overflow-y: auto;
      overflow-x: hidden;
    }
    
    .send {
      position: relative;
      display: flex;
      flex-direction: column;
    
      &-bar {
        flex: 1;
        height: 40px;
        display: flex;
        justify-content: flex-start;
        align-items: center;
        background-color: #ffffff;
    
        .fa {
          padding: 10px 15px;
          cursor: pointer;
        }
      }
    
      &-msg {
        display: flex;
        flex: 1;
        height: 100px;
        overflow: hidden;
        box-shadow: 0 -1px 2px #efefef;
        background-color: #fff;
    
        &-input {
          flex: 1;
          padding: 0 10px;
          box-sizing: border-box;
          border: none;
          line-height: 30px;
          resize: none;
        }
    
        &-btn {
          display: inline-block;
           100px;
          height: 100%;
          line-height: 100px;
          background-color: dodgerblue;
          text-align: center;
          text-decoration: none;
          color: #fff;
        }
      }
    }
    </style>
    

    页面效果没有数据,应该是项目存在问题

  • 相关阅读:
    使用Python的Mock库进行PySpark单元测试
    库龄报表的相关知识
    使用PlanViz进行ABAP CDS性能分析
    Spark SQL中列转行(UNPIVOT)的两种方法
    Spark中的一些概念
    使用Visual Studio Code进行ABAP开发
    2019年的几个目标
    Dom--样式操作
    Dom选择器--内容文本操作
    Javascript面向
  • 原文地址:https://www.cnblogs.com/smart-girl/p/11358872.html
Copyright © 2011-2022 走看看