效果预览 https://www.wangchunjian.top/chat.html
需要用到的库
server端
socket
建立长连接,接收
并向所有建立连接的client推送
消息
笼统的讲就是on
接收客户端消息,emit
向客户端发送消息,详细请移步https://socket.io/
var app = require('express')();
var fs = require('fs');
var privatekey = fs.readFileSync('/www/wdlinux/nginx/conf/cert/www.wangchunjian.top.key', 'utf8');
var certificate = fs.readFileSync('/www/wdlinux/nginx/conf/cert/www.wangchunjian.top.pem', 'utf8');
var options={key:privatekey, cert:certificate};
var https = require('https').Server(options,app);
var io = require('socket.io')(https);
var _ = require('lodash');
let users=[];
app.get('/', function(req, res){
res.send('<h1>Hello world</h1>');
});
io.on('connection',function(socket){
socket.on('chat loading',function(msg){
var findIndex=_.findIndex(users,function(o){return o.chatUserId==msg.chatUserId;});
socket.chatUserId=msg.chatUserId;
console.log(findIndex);
if(findIndex<0){
users.push({chatUserId:msg.chatUserId,nickname:msg.nickname});
socket.broadcast.emit('tooltip',msg.nickname+' join..');
}
io.emit('onlines',users);
});
socket.on('chat message', function(msg){
var findIndex=_.findIndex(users,function(o){return o.chatUserId==msg.chatUserId;});
if(findIndex<0){
users.push({chatUserId:msg.chatUserId,nickname:msg.nickname});
socket.broadcast.emit('tooltip',msg.nickname+' join..');
io.emit('onlines',users);
}
io.emit('chat message',msg);
});
socket.on('nickname', function(msg){
var findIndex=_.findIndex(users,function(o){return o.chatUserId==msg.chatUserId;});
if(findIndex<0){
users.push(msg);
socket.broadcast.emit('tooltip',msg.nickname+' join..');
}else{
users[findIndex]=msg;
}
io.emit('onlines',users);
});
socket.on('disconnect', function(){
var findIndex=_.findIndex(users,function(o){return o.chatUserId==socket.chatUserId;});
var leaveUser=users[findIndex];
if(findIndex>-1){
socket.broadcast.emit('tooltip',leaveUser.nickname+' leave..');
users.splice(findIndex,1);
socket.broadcast.emit('onlines',users);
}
});
});
https.listen(3000, function(){
console.log('listening on *:3000');
});
client端
/views/Chat.vue
引入的一些库
import 'muse-ui-message/dist/muse-ui-message.css';
import Vue from 'vue';
import Message from 'muse-ui-message';
import Toast from 'muse-ui-toast';
var myMarked = require('marked');
import 'highlight.js/styles/github.css';
// Set options
// `highlight` example uses `highlight.js`
myMarked.setOptions({
renderer: new myMarked.Renderer(),
highlight: function(code) {
return require('highlight.js').highlightAuto(code).value;
},
pedantic: false,
gfm: true,
tables: true,
breaks: false,
sanitize: false,
smartLists: true,
smartypants: false,
xhtml: false
});
Vue.use(Message);
Vue.use(Toast,{position:'top'});
import docCookies from './../cookies'
import io from 'socket.io-client';
const uuidv1 = require('uuid/v1')
const socket = io('https://www.wangchunjian.top:3000');
let chatUserId='';
let nickname='匿名';
if(docCookies.hasItem('chatUserId')){
chatUserId=docCookies.getItem('chatUserId');
}else{
chatUserId=uuidv1();
docCookies.setItem('chatUserId',chatUserId,Infinity);
}
if(docCookies.hasItem('nickname')){
nickname=docCookies.getItem('nickname');
}
/views/Chat.vue
里处理Vue事件和属性的代码
<script>
export default {
data () {
return {
chatUserId:chatUserId,
nickname:nickname,
messages:messages,
openFullscreen: false,
onlines:[],
form: {
input: '',
}
}
},
mounted(){
if(this.nickname=='匿名'){
this.EditProfile();
}
var that=this;
socket.on('chat message', function(msg){
msg.msg=myMarked(msg.msg);
that.messages.push(msg);
});
socket.on('onlines', function(msg){
that.onlines=msg;
});
socket.on('tooltip', function(msg){
that.$toast.success(msg);
});
socket.emit('chat loading',{chatUserId:chatUserId,nickname:nickname});
},
methods:{
submit(evt) {
evt.preventDefault();
if(this.form.input!=''){
socket.emit('chat message',{chatUserId:chatUserId,nickname:nickname,msg:this.form.input});
this.form.input='';
}
},
openFullscreenDialog () {
this.openFullscreen = true;
},
closeFullscreenDialog () {
this.openFullscreen = false;
},
EditProfile(){
this.$prompt('请设置昵称', '提示', {
validator (value) {
return {
valid:value.length>1,
message: '请设置昵称至少两个字符'
}
}
}).then(({ result, value }) => {
if (result) {
nickname=value;
this.nickname=value;
socket.emit('nickname',{chatUserId:chatUserId,nickname:value});
docCookies.setItem('nickname',value,Infinity);
}
});
}
}
}
</script>
/views/Chat.vue template
部分
<template>
<div class="about">
<mu-appbar style="width: 100%;" color="primary" id="mu-bar">
<mu-button icon slot="left" @click="openFullscreenDialog">
<mu-icon value="menu"></mu-icon>
</mu-button>
<span v-if="onlines" @click="openFullscreenDialog">聊天室 ({{onlines.length}}) 人在线</span>
<mu-button flat slot="right" @click="EditProfile">设置</mu-button>
</mu-appbar>
<mu-dialog width="360" transition="slide-top" fullscreen :open.sync="openFullscreen">
<mu-appbar color="primary" title="Onlines">
<mu-button slot="left" icon @click="closeFullscreenDialog">
<mu-icon value="close"></mu-icon>
</mu-button>
<mu-button slot="right" flat @click="closeFullscreenDialog">
关闭
</mu-button>
</mu-appbar>
<div class="onlines">
<mu-list>
<mu-list-item avatar button :ripple="false" v-for="u in onlines" :key="u.chatUserId">
<mu-list-item-action>
<mu-avatar>
<img src="/assets/images/me.jpg">
</mu-avatar>
</mu-list-item-action>
<mu-list-item-title>{{ u.nickname }}</mu-list-item-title>
<mu-list-item-action>
<mu-icon value="chat_bubble"></mu-icon>
</mu-list-item-action>
</mu-list-item>
</mu-list>
</div>
</mu-dialog>
<ul id="messages">
<li v-for="(item,index) in messages" :class="item.chatUserId==chatUserId?'me':'you'" :key="index">
<img src="/assets/images/me.jpg" class="avatar" />
<span class="nickname" v-text="item.nickname"></span>
<p class="wrapper" v-html="item.msg"></p>
<div class="clearfix"></div>
</li>
</ul>
<mu-form :model="form" class="mu-demo-form" @submit="submit">
<mu-text-field multi-line :rows="3" v-model="form.input"></mu-text-field>
<mu-button color="primary" type="submit">提交</mu-button>
</mu-form>
</div>
</template>
main.js
入口
import Vue from 'vue'
import Chat from './views/Chat.vue'
import MuseUI from 'muse-ui';
import 'muse-ui/dist/muse-ui.css';
Vue.use(MuseUI);
Vue.config.productionTip = false
new Vue({
render: h => h(Chat)
}).$mount('#app')
index.html
文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>Chat</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic">
<link rel="stylesheet" href="https://cdn.bootcss.com/material-design-icons/3.0.1/iconfont/material-icons.css">
<base target="_blank" />
</head>
<body>
<noscript>
<strong>We're sorry but hello-world doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>