安装Node.js
根据自己的操作系统,去Node.js官网下载安装即可。如果成功安装。在命令行输入node -v
和npm -v
应该能看到相应的版本号。
搭建WebSocket服务端
在本地开发环境,可以换成本地ip地址,或者使用一个虚拟域名指向本地ip。
进入项目根目录建立一个 package.json的文件,添加内容
{
"name": "realtime-server",
"version": "0.0.1",
"description": "my first realtime server",
"dependencies": {}
}
接下来使用npm命令安装express和socket.io
在cmd中执行
npm install --save express
npm install --save socket.io
安装成功后,应该可以看到工作目录下生成了一个名为
node_modules
的文件夹,里面分别是
express
和
socket.io
,接下来可以开始编写服务端的代码了,新建一个文件:
index.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res){
res.send('<h1>Welcome Realtime Server</h1>');
});
http.listen(3000, function(){
console.log('listening on *:3000');
});
命令行运行node index.js
,如果一切顺利,你应该会看到返回的listening on *:3000
字样,这说明服务已经成功搭建了。此时浏览器中打开http://localhost:3000
应该可以看到正常的欢迎页面。
如果你想要让服务运行在线上服务器,并且可以通过域名访问的话,可以使用Nginx做代理,在nginx.conf中添加如下配置,然后将域名(比如:zou
.com)解析到服务器IP即可。
server
{
listen 80;
server_name realtime.plhwin.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
完成以上步骤,http://
zou.com:3000的后端服务就正常搭建了。
服务端代码实现
前面讲到的index.js
运行在服务端,之前的代码只是一个简单的WebServer欢迎内容,让我们把WebSocket服务端完整的实现代码加入进去,整个服务端就可以处理客户端的请求了。完整的index.js
代码如下:
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res){
res.send('<h1>Welcome Realtime Server</h1>');
});
//在线用户
var onlineUsers = {};
//当前在线人数
var onlineCount = 0;
io.on('connection', function(socket){
console.log('a user connected');
//监听新用户加入
socket.on('login', function(obj){
//将新加入用户的唯一标识当作socket的名称,后面退出的时候会用到
socket.name = obj.userid;
//检查在线列表,如果不在里面就加入
if(!onlineUsers.hasOwnProperty(obj.userid)) {
onlineUsers[obj.userid] = obj.username;
//在线人数+1
onlineCount++;
}
//向所有客户端广播用户加入
io.emit('login', {onlineUsers:onlineUsers, onlineCount:onlineCount, user:obj});
console.log(obj.username+'加入了聊天室');
});
//监听用户退出
socket.on('disconnect', function(){
//将退出的用户从在线列表中删除
if(onlineUsers.hasOwnProperty(socket.name)) {
//退出用户的信息
var obj = {userid:socket.name, username:onlineUsers[socket.name]};
//删除
delete onlineUsers[socket.name];
//在线人数-1
onlineCount--;
//向所有客户端广播用户退出
io.emit('logout', {onlineUsers:onlineUsers, onlineCount:onlineCount, user:obj});
console.log(obj.username+'退出了聊天室');
}
});
//监听用户发布聊天内容
socket.on('message', function(obj){
//向所有客户端广播发布的消息
io.emit('message', obj);
console.log(obj.username+'说:'+obj.content);
});
});
http.listen(3000, function(){
console.log('listening on *:3000');
});
客户端代码实现
在该目录下 建立一个index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="format-detection" content="telephone=no"/>
<meta name="format-detection" content="email=no"/>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0" name="viewport">
<title>多人聊天室</title>
<link rel="stylesheet" type="text/css" href="./style.css" />
<!--[if lt IE 8]><script src="./json3.min.js"></script><![endif]-->
<script src="http://自己的ip或者域名:3000/socket.io/socket.io.js"></script>
</head>
<body>
<div id="loginbox">
<div style="width:260px;margin:200px auto;">
请先输入你在聊天室的昵称
<br/>
<br/>
<input type="text" style="width:180px;" placeholder="请输入用户名" id="username" name="username" />
<input type="button" style="width:50px;" value="提交" οnclick="CHAT.usernameSubmit();"/>
</div>
</div>
<div id="chatbox" style="display:none;">
<div style="background:#3d3d3d;height: 28px; width: 100%;font-size:12px;">
<div style="line-height: 28px;color:#fff;">
<span style="text-align:left;margin-left:10px;">Websocket多人聊天室</span>
<span style="float:right; margin-right:10px;"><span id="showusername"></span> |
<a href="javascript:;" οnclick="CHAT.logout()" style="color:#fff;">退出</a></span>
</div>
</div>
<div id="doc">
<div id="chat">
<div id="message" class="message">
<div id="onlinecount" style="background:#EFEFF4; font-size:12px; margin-top:10px; margin-left:10px; color:#666;">
</div>
</div>
<div class="input-box">
<div class="input">
<input type="text" maxlength="140" placeholder="请输入聊天内容,按Ctrl提交" id="content" name="content">
</div>
<div class="action">
<button type="button" id="mjr_send" οnclick="CHAT.submit();">提交</button>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="./client.js"></script>
</body>
</html>
上面的html内容本身没有什么好说的,我们主要看看里面的4个文件请求:
1、
realtime.plhwin.com:3000/socket.io/socket.io.js
2、
style.css
3、client.js
第1个JS是Socket.IO提供的客户端JS文件,在前面安装服务端的步骤中,当npm安装完socket.io并搭建起WebServer后,这个JS文件就可以正常访问了。
第2个style.css文件没什么好说的,就是样式文件而已。
第4个client.js
是完整的客户端的业务逻辑实现代码,它的内容如下:
(function () {
var d = document,
w = window,
p = parseInt,
dd = d.documentElement,
db = d.body,
dc = d.compatMode == 'CSS1Compat',
dx = dc ? dd: db,
ec = encodeURIComponent;
w.CHAT = {
msgObj:d.getElementById("message"),
screenheight:w.innerHeight ? w.innerHeight : dx.clientHeight,
username:null,
userid:null,
socket:null,
//让浏览器滚动条保持在最低部
scrollToBottom:function(){
w.scrollTo(0, this.msgObj.clientHeight);
},
//退出,本例只是一个简单的刷新
logout:function(){
//this.socket.disconnect();
location.reload();
},
//提交聊天消息内容
submit:function(){
var content = d.getElementById("content").value;
if(content != ''){
var obj = {
userid: this.userid,
username: this.username,
content: content
};
this.socket.emit('message', obj);
d.getElementById("content").value = '';
}
return false;
},
genUid:function(){
return new Date().getTime()+""+Math.floor(Math.random()*899+100);
},
//更新系统消息,本例中在用户加入、退出的时候调用
updateSysMsg:function(o, action){
//当前在线用户列表
var onlineUsers = o.onlineUsers;
//当前在线人数
var onlineCount = o.onlineCount;
//新加入用户的信息
var user = o.user;
//更新在线人数
var userhtml = '';
var separator = '';
for(key in onlineUsers) {
if(onlineUsers.hasOwnProperty(key)){
userhtml += separator+onlineUsers[key];
separator = '、';
}
}
d.getElementById("onlinecount").innerHTML = '当前共有 '+onlineCount+' 人在线,在线列表:'+userhtml;
//添加系统消息
var html = '';
html += '<div class="msg-system">';
html += user.username;
html += (action == 'login') ? ' 加入了聊天室' : ' 退出了聊天室';
html += '</div>';
var section = d.createElement('section');
section.className = 'system J-mjrlinkWrap J-cutMsg';
section.innerHTML = html;
this.msgObj.appendChild(section);
this.scrollToBottom();
},
//第一个界面用户提交用户名
usernameSubmit:function(){
var username = d.getElementById("username").value;
if(username != ""){
d.getElementById("username").value = '';
d.getElementById("loginbox").style.display = 'none';
d.getElementById("chatbox").style.display = 'block';
this.init(username);
}
return false;
},
init:function(username){
/*
客户端根据时间和随机数生成uid,这样使得聊天室用户名称可以重复。
实际项目中,如果是需要用户登录,那么直接采用用户的uid来做标识就可以
*/
this.userid = this.genUid();
this.username = username;
d.getElementById("showusername").innerHTML = this.username;
this.msgObj.style.minHeight = (this.screenheight - db.clientHeight + this.msgObj.clientHeight) + "px";
this.scrollToBottom();
//连接websocket后端服务器
this.socket = io.connect('ws://自己的ip或者域名:3000');
//告诉服务器端有用户登录
this.socket.emit('login', {userid:this.userid, username:this.username});
//监听新用户登录
this.socket.on('login', function(o){
CHAT.updateSysMsg(o, 'login');
});
//监听用户退出
this.socket.on('logout', function(o){
CHAT.updateSysMsg(o, 'logout');
});
//监听消息发送
this.socket.on('message', function(obj){
var isme = (obj.userid == CHAT.userid) ? true : false;
var contentDiv = '<div>'+obj.content+'</div>';
var usernameDiv = '<span>'+obj.username+'</span>';
var section = d.createElement('section');
if(isme){
section.className = 'user';
section.innerHTML = contentDiv + usernameDiv;
} else {
section.className = 'service';
section.innerHTML = usernameDiv + contentDiv;
}
CHAT.msgObj.appendChild(section);
CHAT.scrollToBottom();
});
}
};
//通过“回车”提交用户名
d.getElementById("username").onkeydown = function(e) {
e = e || event;
if (e.keyCode === 13) {
CHAT.usernameSubmit();
}
};
//通过“回车”提交信息
d.getElementById("content").onkeydown = function(e) {
e = e || event;
if (e.keyCode === 13) {
CHAT.submit();
}
};
})();
style.css
@media (max-device-width: 480px) and (orientation: portrait) {
@-ms-viewport{
width:320px;
}
}
*, *:before, *:after {
-webkit-box-sizing:border-box;
box-sizing:border-box;
-webkit-text-size-adjust:none;
-ms-text-size-adjust:none;
text-size-adjust:none;
-webkit-tap-highlight-color:rgba(0, 0, 0, 0);
}
body, div, dl, dt, dd, ul, ol, li, h1, h2, h3,
h4, h5, h6, pre, code, form, field set, legend,
input, textarea, p, blockquote, th, td {
margin:0;
padding:0;
-webkit-appearance:none;
}
table {
border-collapse:collapse;
border-spacing:0;
}
fieldset, img {
border:0;
}
address, caption, cite, code,
dfn, em, strong, th, var {
font-style:normal;
font-weight:normal;
}
li {
list-style:none;
}
caption, th {
text-align:left;
}
h1, h2, h3, h4, h5, h6 {
font-size:100%;
font-weight:normal;
}
q:before,
q:after {
content:"";
}
abbr, acronym {
border:0;
font-variant:normal;
}
sup {
vertical-align:text-top;
}
sub {
vertical-align:text-bottom;
}
input, textarea, select {
font-family:inherit;
font-size:inherit;
font-weight:inherit;
}
input:password {
ime-mode:disabled;
}
*:focus {
outline:none;
}
ul {
margin:0;
list-style:none;
}
a{
color:#2982e5;
text-decoration:none;
}
a:active {
color:#f60;
}
.smaller {
font-size:.8em
}
body {
background-color:#efeff4;
}
#doc {
height: 100%;
line-height:1.4;
}
#chat-robot {
padding:4px;
padding-bottom:0;
border:1px solid #7bb0e2;
background-color:#e6f5fc;
}
#mask {
display:table;
position:absolute;
top:0;
height:100%;
width:100%;
background-color:#fff;
z-index:100;
}
#mask div {
display:table-cell;
height:100%;
vertical-align:middle;
text-align:center;
}
/*guide mask begin*/
.guide-mask,.guide-mask .mask-top{
position: absolute;
height: 100%;
width: 100%;
}
.guide-mask{
top:0;
left:0;
z-index: 90;
}
.guide-mask .mask-top{
bottom: 44px;
background: rgba(0,0,0,.7);
z-index: 90;
}
.guide-mask .mask-bottom{
position: absolute;
bottom: 0;
width: 100%;
height: 44px;
background:rgba(255, 255, 255, 0);
z-index: 90;
}
.mask-top .iknow-btn{
position: absolute;
width: 256px;
height: 213px;
bottom:0;
left:50%;
margin-left:-128px;
background:url('https://i.alipayobjects.com/i/ecmng/png/201404/2NcJmE3ldt.png') no-repeat;
background-size: 100%;
z-index: 91;
}
/*guide mask end*/
/*notice begin*/
.notice-wrap-out{
/*min-height:41px; */
max-height: 56px;
}
.notice-wrap{
position:fixed;
top:0;
display:-webkit-box;
width:100%;
background:rgba(255, 255, 255, .95);
/*line-height:41px;
padding-left:10px;*/
padding:10px 0;
font-size: 13px;
z-index:89;
}
.delNotice-wrap{
padding:0 10px 0 10px;
/*padding:13px 10px;*/
}
.icon-notice,.icon-delNotice{
width:15px;
height:15px;
background:url('https://i.alipayobjects.com/i/ecmng/png/201403/2MRvjlV1Vd.png') no-repeat 0 0;
background-size:100%;
}
.text-notice{
-webkit-box-flex:1;
margin-left:10px;
/*white-space:nowrap;*/
/*text-overflow:ellipsis;*/
overflow: hidden;
}
.icon-delNotice{
background-position: 0 -20px;
}
.icon-notice{
margin-top:13px;
}
/*notice end*/
.action button {
border:none;
border-radius:30px;
color:#8c8c8c;
height:28px;
width:68px;
text-align:center;
background:#fbfbfb;
box-shadow:0 0 1px #000,0 2px 2px #eee;
}
.action button:active,
.action button[disabled] {
color:#fff;
background-color:#0044cc;
}
.action button:focus {
outline:thin dotted #333;
outline:5px auto -webkit-focus-ring-color;
outline-offset:-2px;
}
.action button[disabled] {
cursor:default;
background-color:#ccc;
background-image:none;
border-color:#ccc;
box-shadow:none;
}
.fn-hide {
display:none;
}
.message {
border-bottom:0;
background-color:#efeff4;
/*min-height:300px;*/
padding-bottom: 100px;
}
.message section a{
/*white-space:nowrap*/
}
.message:after {
display:block;
clear:both;
height:0;
content:"\0020";
}
section,div.robot {
padding:10px;
clear:both;
}
div.robot,section.robot, section.service,section.top-message {
float:left;
}
div.robot>div,
section.robot>div,
section.service>div,
section.top-message>div{
max-width: 225px;
display: inline-block;
position:relative;
padding:12px;
margin:0 20px 0 10px;
border-radius:10px;
background-color:#fff;
box-shadow: inset 0 0 1px #fff;
vertical-align: top;
}
section.robot>div{
word-break: break-word;
}
/*
div.robot>div,
section.robot>div{
margin: 0 11px 0 45px;
} */
div.robot>div:before,
section.robot>div:before,
section.service>div:before,
section.top-message>div:before,
.system .windowContent:before{
content:"";
position: absolute;
left: -5px;
top: 4px;
width: 0;
height: 0;
border-top: solid transparent;
border-right: 7px solid #fff;
border-bottom: 4px solid transparent;
}
/*
div.robot>div:after,
section.robot>div:after,
section.service>div:after,
section.top-message>div:after,
.system .windowContent:after {
border-color:rgba(227, 236, 255, 0);
border-right-color:#c5e4fa;
border-width:8px;
top:15px;
margin-top:-8px;
}
div.robot>div:before,
section.robot>div:before,
section.service>div:before,
section.top-message>div:before,
.system .windowContent:before {
border-color:rgba(165, 181, 213, 0);
border-right-color:#fff;
border-width:9px;
top:15px;
margin-top:-9px;
}
*/
section.user {
float:right;
text-align:right;
}
section.user>div {
max-width: 225px;
position:relative;
display: inline-block;
padding:8px;
margin:0 10px 0 12px;
text-align:left;
border-radius:10px;
color:#fff;
box-shadow: inset 0 0 1px #007aff;
background-color:#007aff;
vertical-align: top;
word-break: break-all;
}
/*
section.user>div:after,
section.user>div:before {
left:100%;
border:solid transparent;
content:" ";
height:0;
width:0;
position:absolute;
pointer-events:none;
}
section.user>div:after {
border-color:rgba(255, 255, 255, 0);
border-left-color:#e3e3e3;
border-width:8px;
top:15px;
margin-top:-8px;
}
*/
section.user>div:before {
content:"";
position: absolute;
right: -5px;
top: 4px;
width: 0;
height: 0;
border-top: solid transparent;
border-left: 7px solid #007aff;
border-bottom: 4px solid transparent;
}
header {
font-weight:700;
color:#4d4d4d;
}
.welcome em{
color:#2982e5;
}
.welcome {
color:#000;
font-size:16px;
margin-bottom: 0;
}
.welcome span, .user>span {
font-weight:700;
color:#f60;
}
.user>span {
font-weight:normal;
font-size:14px;
color:#f60;
max-width: 60px;
height: 36px;
overflow: hidden;
float: right;
}
.robot>span, .service>span {
color:#006e9c;
font-weight:700;
}
.service>span{
max-width: 60px;
height: 36px;
/*
background: url("https://i.alipayobjects.com/i/ecmng/png/201404/2O35FCjByn.png");
color: transparent;
background-size: 100%;
*/
font-weight:normal;
font-size:14px;
color:#999;
overflow: hidden;
float: left;
}
.service>time{
display: none;
}
.top-message article{
color: #ff6600;
}
.top-message article .init-temp{
color: #000;
}
.top-message article .topHeader{
color: #ff6600;
}
.top-message ul{
padding: 0;
}
.top-message ul,.top-message li{
list-style: none;
}
.robot>span ,.top-message>span{
float: left;
}
.service>time{
display: none;
}
.robot>span ,.top-message>span{
float: left;
}
.system {
text-align:center;
color:#999;
font-size:.8em;
}
.system .windowimg{
float: left;
}
.system .windowContent{
position: relative;
border: solid 1px #7bb2d9;
border-radius: 5px;
background-color: #c5e4fa;
padding: 8px;
margin: 0 12px 0 45px;
text-align: left;
color: #000;
font-size: 14px;
}
time {
color:#909090;
font-size:.8em;
}
.feedback {
position:relative;
margin:8px 0;
padding:4px 8px;
border:1px solid #fad58c;
background-color:#fffeeb;
}
.feedback .arrow {
position:absolute;
display:block;
width:10px;
height:10px;
font-size:0;
background:#fffeeb;
border-left:1px solid #fad58c;
border-top:1px solid #fad58c;
top:-7px;
left:16px;
-webkit-transform:rotate(45deg);
-ms-transform:rotate(45deg);
-moz-transform:rotate(45deg);
-o-transform:rotate(45deg);
transform:rotate(45deg);
}
.input-box {
display:-webkit-box;
display:-ms-flexbox;
display:-moz-box;
position:fixed;
bottom: 0;
padding:8px 10px;
width:100%;
box-shadow:0 0 1px #000;
background-color:#fff;
height: 100px;
}
.input-box .input {
-webkit-box-flex:1;
-ms-flex:1;
-moz-box-flex:1;
background-color:#fff;
-webkit-box-flex:1;
/*height: 28px;*/
line-height:25px;
}
.input input {
margin:0;
float: left;
padding:0 6px;
height:28px;
width:93%;
font-size:14px;
background-color: #fbfbfb;
border:none;
-webkit-border-radius:15px;
border-radius:15px;
color:#000;
box-shadow:0 0 1px #000;
}
.input input[disabled] {
background-color:#eee;
}
.input-box button {
font-size: 15px;
/*margin-left:11px;*/
float: right;
}
section.robot>.robot-comment {
padding: 0;
}
.robot-comment .robot-comment-title{
padding: 8px;
}
.service-comment button, .robot-comment button {
padding:2px 12px;
margin:0 8px;
}
.robot-comment button{
margin: 0;
}
section.service-comment>div{
padding: 0;
padding-top: 10px;
}
.message .robot .btn-robot{
padding: 0;
}
.btn-robot .btn-title{
padding: 8px;
}
.suggest {
border:1px solid #afafaf;
border-bottom:0 none;
background-color: rgba(255,255,255, .95);
width:100%;
}
.suggest ul {
margin:0;
padding:0;
list-style:none;
}
.suggest li {
height: 44px;
line-height: 44px;
padding-left: 10px;
border-bottom:1px solid #afafaf;
font-size: 17px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
.ui-tab-wrap {
padding:5px 10px;
background-color:#e4e3e8;
}
.ui-tab {
height:30px;
line-height:26px;
border-radius:5px;
-webkit-border-radius:5px;
-o-border-radius:5px;
-ms-border-radius:5px;
font-size:14px;
}
.ui-tab-item {
display:inline-block;
width:49%;
text-align:center;
text-decoration:none;
}
.ui-tab-item-right {
float:right;
background: #ebebeb;
border: 1px solid #bababa;
border-radius: 0 5px 5px 0;
color: #999;
}
.ui-tab .ui-tab-item-right:active,.ui-tab .ui-tab-item-right:visited,.ui-tab .ui-tab-item-right:link{
color: #999;
}
.ui-tab-item-active {
float: right;
background-color:#6a89c4;
background-color: #2059b0;
border: 1px solid #2059b0;
border-radius: 5px 0 0 5px;
}
.ui-tab-item,
.ui-tab-item:active,
.ui-tab-item:visited,
.ui-tab-item:link {
color:#6a89c4
}
.ui-tab-item-active,
.ui-tab-item-active:active,
.ui-tab-item-active:visited,
.ui-tab-item-active:link {
color:#fff;
}
.windowContent input{
padding: 13px 10px 12px;
width: 100%;
display: block;
border-radius: 8px;
border: 1px solid #ccc;
background: #fff;
}
.windowContent .revaliValue{
margin-top: 8px;
}
.windowContent .error-p{
color: #f60;
margin-top: 5px;
}
.windowContent .button-p{
margin-top: 15px;
}
.init-temp li a{
color: #2982e5;
}
.recommend ul li,.recommend ul li a{
color: #2982e5;
}
section.robot>.comment-btns,section.service>.comment-btns{
padding: 0;
}
.comment-btns .comment-title{
padding: 8px;
}
.comment-btns section{
padding: 0;
margin-bottom: 0;
border-top:1px solid #7bb3db;
}
.btn-container{
padding: 0;
margin-bottom: 0;
border-top:1px solid #7bb3db;
}
.service-comment button{
padding: 2px 15px;
height: 30px;
background: none;
border: none;
border-right: 1px solid #7bb3db;
margin: 0;
}
.service-comment button:last-child{
border-right:none;
}
.btn-no{
border-right: 1px solid #7bb3db;
}
.robot-comment .isClicked{
color: #f60;
}
.robot-comment button img{
margin-right: 10px;
}
.service-comment button span{
width: 18px;
height: 18px;
display: inline-block;
margin-right: 2px;
}
.service-comment button .btn-3{
background: url("https://i.alipayobjects.com/e/201312/1eg8I2tUAn.png") no-repeat;
}
.service-comment button .btn-2{
background: url("https://i.alipayobjects.com/e/201312/1egEIXEjv3.png") no-repeat;
}
.service-comment button .btn-1{
background: url("https://i.alipayobjects.com/e/201312/1egEVvyvdN.png") no-repeat;
}
.robot-comment button{
border: none;
background: none;
width:110px;
height:35px;
line-height:35px;
}
button.btn-no{
border-radius:0 0 0 8px;
}
button.btn-yes{
border-radius:0 0 8px 0;
}
.robot-comment button:last-child{
border-left: 1px solid #7bb3db;
}
.robot-comment .isClicked,.service-comment .isClicked{
background: #93c3e5;
}
button:active, button[disabled]{
color: #999;
}
.windowContent .button-p{
margin-top: 15px;
border-top: 1px solid #77b0d9;
}
.J-confirm{
width: 117px;
height: 30px;
border: none;
background: none;
border-left: 1px solid #77b0d9;
}
.J-cancel{
width: 124px;
height: 30px;
background: none;
border: none;
}
.button-p button[disabled]{
cursor: default;
background-color: none;
background-image: none;
border-color: none;
box-shadow: none;
}
.button-p button:active,.button-p button[disabled]{
background-color: none;
}
.btnClicked{
background: #93c3e5;
}
.user .avatar-img,.avatar-service{
width: 34px;
height: 34px;
border-radius: 2px;
}
section article header{
margin-top:10px;
}
section article li{
width: 202px;
padding:10px 0;
list-style:none;
border-top: solid 1px #afafaf;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
color:#2982e5;
}
section article li:last-child{
padding-bottom:0;
}
section,section a{
font-size:16px;
}
.arrow-down,.arrow-up{
width:10px;
height:6px;
background:url("https://i.alipayobjects.com/i/ecmng/png/201404/2O4b2MEDzR.png") no-repeat;
background-size:100%;
padding:5px 0;
margin:5px auto 0;
}
.arrow-up{
background:url("https://i.alipayobjects.com/i/ecmng/png/201404/2O4b2MEDzR.png") no-repeat 0 -16px;
background-size:100%;
}
section .msg-system{
color: #999;
font-size: 13px;
}
section .msg-system a{
font-size:13px;
}
section .msg-system a.isClicked{
color:#999;
}
section.evaluateSec .answer{
padding: 8px 12px 4px 12px;
}
.poll-loading{
/*position: absolute;*/
height: 24px;
width: 24px;
margin:0 auto;
text-align: center;
background: #ff9e3e;
background:url('https://i.alipayobjects.com/i/ecmng/gif/201404/2VZeenZ86v.gif') no-repeat;
background-size: 100%;
z-index:86;
}
/**layer**/
.layer{
display: none;
position: absolute;
width: 275px;
background: rgba(255, 255, 255, .95);
border-radius: 7px;
z-index: 120;
}
.layer .layer-hd{
font-size: 16px;
line-height: 41px;
text-align: center;
}
.layer .layer-ct{
padding:0 15px 15px 15px;
text-align: center;
}
.layer .layer-ct input.tel-input{
width: 230px;
height: 30px;
border: solid 1px #ccc;
border-radius:5px;
padding:0 4px;
}
.layer .layer-ft{
border-top:solid 1px #afafaf;
font-size: 16px;
line-height: 41px;
text-align: center;
}
.layer-ft button, .evaluateWrap button{
border:none;
background-color:#fff;
opacity: .95;
height: 44px;
line-height: 44px;
text-align: center;
color:#007aff;
width: 135px;
font-size: 16px;
}
.evaluateWrap button{
width:85px;
height:28px;
line-height:24px;
}
.layer .button-left, .evaluateWrap .button-cp{
border-radius: 0 0 0 7px;
border-right: solid 1px #afafaf;
}
.layer .button-right{
border-radius: 0 0 7px 0;
}
.layer-mask{
background-color: rgba(0,0,0,.6);
position: absolute;
top:0;
z-index: 110;
}
.layer .err-msg{
color:red;
font-size: 12px;
text-align: left;
margin-top:5px;
padding-left:5px;
}
.evaluateWrap .title{
border-bottom:solid 1px #afafaf;
padding-bottom:8px;
margin-bottom:5px;
font-size: 16px;
color: #000;
}
.bad-evaluate-wrap{
width: 290px;
}
.bad-evaluate-wrap .layer-hd{
border-bottom: solid 1px #afafaf;
}
.layer .evaluate-list li{
line-height: 44px;
border-bottom: solid 1px #afafaf;
}
.layer .evaluate-list li:after,.other-wrap:after{
display:block;
clear:both;
height:0;
content:"\0020";
}
.layer .evaluate-list li:last-child{
border-bottom: none;
}
.layer .evaluate-list li label{
float: left;
width:250px;
overflow: hidden;
}
.layer .evaluate-list li input{
float: right;
margin-top: 16px;
vertical-align: bottom;
-webkit-appearance:checkbox;
}
.layer .evaluate-list{
padding:0 10px;
text-align: left;
}
.text-wrap{
border-top: solid 1px #afafaf;
}
.text-wrap textarea{
padding: 4px;
width: 265px;
height: 44px;
border: none;
}
.layer button[disabled],
.evaluateWrap button[disabled],
.evaluateWrap button.isClicked{
color:#afafaf;
}
.history-date{
border-radius: 3px;
background: #afafaf;
color: #fff;
width: 100px;
margin: 0 auto;
height: 18px;
padding: 0 4px;
line-height: 20px;
}
最后大家 用cmd进入自己的工作目录开始监听端口
node index.js
这样就可以简单的实现了群聊