【珍惜时间】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 {
width: 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;
width: 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;
width: 100%;
text-align: center;
}
.login-btn {
display: inline-block;
margin-top: 20px;
width: 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> |
<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;
width: 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;
width: 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;
width: 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;
width: @height;
border-radius: 50%;
}
& :hover,
& :active {
background: #efefef;
}
.noread {
display: inline-block;
background-color: #f00;
color: #fff;
min-width: 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>
//src\views\pages\chat.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;
width: 100px;
height: 100%;
line-height: 100px;
background-color: dodgerblue;
text-align: center;
text-decoration: none;
color: #fff;
}
}
}
</style>
页面效果没有数据,应该是项目存在问题
【珍惜时间】vue-websocket的更多相关文章
- Vue+WebSocket+ES6+Canvas 制作「你画我猜」小游戏
Vue+WebSocket+ES6+Canvas 制作「你画我猜」小游戏 转载 来源:jrainlau 链接:https://segmentfault.com/a/1190000005804860 项 ...
- N年之后,只记得三井寿!而我们程序猿们也要加油珍惜时间!
[感觉程序员看一篇励志文章效果大于6篇技术文章,3份源码下载.....所以上此文] [说明:本文不少段落是摘自别人文章,因为本人写程序的文笔有限,怕感动不了大家,所以摘取了不错的部分] 前段时间重新看 ...
- vue+websocket+express+mongodb实战项目(实时聊天)
继上一个项目用vuejs仿网易云音乐(实现听歌以及搜索功能)后,发现上一个项目单纯用vue的model管理十分混乱,然后我去看了看vuex,打算做一个项目练练手,又不想做一个重复的项目,这次我就放弃颜 ...
- vue+websocket+express+mongodb实战项目(实时聊天)(二)
原项目地址:[ vue+websocket+express+mongodb实战项目(实时聊天)(一)][http://blog.csdn.net/blueblueskyhua/article/deta ...
- SpringBoot+Vue+WebSocket 实现在线聊天
一.前言 本文将基于 SpringBoot + Vue + WebSocket 实现一个简单的在线聊天功能 页面如下: 在线体验地址:http://www.zhengqingya.com:8101 二 ...
- vue+websocket demo 实例
vue+websocket demo: <!-- vue + websocket连接demo --> <template> <div class="" ...
- vue + websocket 的使用
阳光正好,我们正在努力前行. 一.引言 初始使用websocket ,一开始看文档的时候,觉得很简单,只需要创建websocket实例,然后有几个监听打开连接,监听关闭连接,监听连接异常等方法.但是, ...
- Vue+WebSocket 实现页面实时刷新长连接
最近vue项目要做数据实时刷新,折线图每秒重画一次,数据每0.5秒刷新一次,说白了就是实时刷新,因为数据量较大,用定时器估计页面停留一会就会卡死... 与后台人员讨论过后决定使用h5新增的WebSoc ...
- 【珍惜时间】iReport
项目很点意思,感觉很高超的样子 先放下项目的github地址:https://github.com/tctangyanan/iReport 感谢各位伟大的程序员无私的分享自己的技术 老规矩,我们会运行 ...
随机推荐
- docker网络原理
以下内容引用Docker -- 从入门到实践 当 Docker 启动时,会自动在主机上创建一个 docker0 虚拟网桥,实际上是 Linux 的一个 bridge,可以理解为一个软件交换机.它会在挂 ...
- Delphi 2010 中的泛型
Delphi 2010 中的泛型 2010已发布很长时间了,口碑还不错,准备用它开发下一项目,但对泛型等新东西的认识还不够,就搜了一下,发现下面这篇文章,还不错,大家一起补补课吧! C++中的模板.C ...
- due to a StackOverflowError. Possible root causes include a too low setting for -Xss and illegal cyclic inheritance dependencies. The class hierarchy being processed was [org.jaxen.util.AncestorAxisIt
七月 31, 2019 4:39:01 下午 org.apache.catalina.startup.VersionLoggerListener log信息: Server version: Apac ...
- JS Date Math Number
Date对象 Date对象和String对象不太一样,定义了一字符串,其实就是一个String对象,就可以直接调用属性和方法. Date对象的使用,必须使用new关键字来创建,否则,无法调用Date对 ...
- socket的多线程实现
步骤: 1.服务端创建ServerSocket,循环调用accept()等待客户端连接: 2.客户端创建socket并请求与服务端对话: 3.服务端接收客户端的请求,创建socket与客户端进行专线连 ...
- 2-MySQL高级-事务-基本概念(1)
事务 1. 为什么要有事务 事务广泛的运用于订单系统.银行系统等多种场景 例如: A用户和B用户是银行的储户,现在A要给B转账500元,那么需要做以下几件事: 检查A的账户余额>500元: A ...
- MySQL数据库之DML(数据操作语言)
对表记录的增删改 1.MySQL之DML创建数据表user create table user( id int unsigned not null auto_increment primary key ...
- 【JZOJ4905】【BZOJ4720】【luoguP1850】换教室
description 对于刚上大学的牛牛来说,他面临的第一个问题是如何根据实际情况申请合适的课程.在可以选择的课程中,有2n节课程安排在n个时间段上.在第i(1≤i≤n)个时间段上,两节内容相同的课 ...
- windows 嵌入控制台
{ 实际非常简单 需要控制台的hwnd 和 hdc 能获取控制台的hwnd 那hdc 就出来了 有了hdc 还有什么不能干的呢?? 如果会win32 窗口编程的就知道hdc,是一个让人流口水的类型 } ...
- Jmeter使用:JSON返回数据处理
想要解决的问题: 通过查询接口,获取response数据,作为下个请求post的参数值 后置处理器:JSON Extractor 先下载一个插件:JSONPathExtractor Names of ...