1、準備工作
https://socket.io/get-started/chat/
不一定要使用uni-app,也可以使用其它
2、node核心代碼
const express = require("express");
const app = express();
let ioListen = app.listen("8082");
let io = require("socket.io").listen(ioListen);
let count = 0;
io.on('connection', (socket) => {
socket.on("ag-message",data=>{
socket.broadcast.emit("ag-gbmsg",data);//廣播出去
});
socket.on("welcome",data=>{
count = count + 1;
socket.broadcast.emit("ag-welcome",{//自己不可以見
info:data.info,
count:count
});
socket.emit("ag-welcome",{//自己可見
info:data.info,
count:count
});
});
socket.on("deleteCount",data=>{
count = count - 1;
socket.broadcast.emit("updateCount",data,count);
});
});
app.listen("6070",()=>{
console.log("http://localhost:6070");
});
module.exports = app;
3、uni-app核心代碼
<template>
<view class="chat box">
<view class="online">當前{{count}}人在線</view>
<view class="chat-main">
<scroll-view :scroll-top="scrollTop" :style="{height:chatHeight+'rpx'}" scroll-y="true">
<template v-for="(item,index) in arr">
<view :class="[item.class,'item']" :key="index">
<template v-if="item.class == 'center'">
<view class="info">{{item.info}}</view>
</template>
<template v-else-if="item.class == 'left'">
<view class="user-icon">
<image :src="item.imgUrl" class="user-img"></image>
</view>
<view class="left-icon"></view>
<text class="user-info">{{item.info}}</text>
</template>
<template v-else-if="item.class == 'right'">
<text class="user-info">{{item.info}}</text>
<view class="right-icon"></view>
<view class="user-icon">
<image :src="item.imgUrl" class="user-img"></image>
</view>
</template>
</view>
</template>
<view class="gundong"></view>
</scroll-view>
</view>
<view class="send">
<view class="send-box">
<input type="text" class="input" v-model="text" />
<view class="img">
<image :src="lng" class="icon" @click="func(0)"></image>
</view>
<view class="img">
<image :src="send" class="icon" @click="func(1)"></image>
</view>
</view>
</view>
<uni-popup ref="popup" type="bottom">
<view class="used-list">
<view class="used-item" v-for="(item,index) in usedArr" :key="index" @click="usedClick(item)">
{{item}}
</view>
</view>
</uni-popup>
</view>
</template>
<script>
let vm;
let imgUrl = getApp().globalData.imgUrl;
import {debounce} from "@/common/public.js"
import uniPopup from '@/components/uni-popup/uni-popup.vue'
export default {
data() {
return {
count:0,
user:{
name:"",
logo:""
},
text:"",//文本框內容
lng:imgUrl + "jstx-lng.png",
send:imgUrl + "jstx-send.png",
arr:[],//class:left表示左邊,center表示中間,right表示右邊
chatHeight:0,
oldbottom:0,
scrollTop:0,
usedArr:[
"熱烈歡迎新朋友加入",
"鼓掌鼓掌",
"不好意思,有點事,先離開一會",
"呵呵呵呵"
]
};
},
onLoad(e) {
let that = this;
vm = that;
that.user.name = e.user;
that.user.logo = e.img;
that.$nextTick(()=>{
let query = uni.createSelectorQuery();
query.select(".chat-main").boundingClientRect(elem=>{
that.chatHeight = (elem.height - 10) * 2;
}).exec();
query.select(".gundong").boundingClientRect((res)=>{
this.oldbottom = res.bottom;
}).exec();
that.getMsg();
that.join(e.user);
that.welcome();
that.updateCount();
uni.setStorage({
key:"user",
data:e.user
});
});
},
methods:{
func:debounce((type)=>{
if(type == 0){
vm.$refs.popup.open();
}else{//發送
if(vm.text.length > 0){//不能發送爲空
let info = {
class:"right",
info:vm.user.name + "\n" + vm.text,
imgUrl:vm.user.logo || imgUrl + "jstx-ag.png"
}
let info_ = {
class:"left",
info:vm.user.name + "\n" + vm.text,
imgUrl:vm.user.logo || imgUrl + "jstx-ag.png"
}
vm.arr.push(info);
vm.socket.emit("ag-message",info_);
vm.text = "";
}else{
uni.showToast({
title:"不能發送空白消息",
icon:"none"
});
return;
}
}
}),
getMsg(){
this.socket.on("ag-gbmsg",data=>{
this.arr.push(data);
});
},
join(name){
let data = {
class:"center",
info:"歡迎 " + name + " 加入控制檯"
}
this.socket.emit("welcome",data);
},
welcome(){
let that = this;
this.socket.on("ag-welcome",data=>{
that.arr.push({
class:"center",
info:data.info
});
that.count = data.count;
});
},
updateCount(){
let that = this;
this.socket.on("updateCount",(data,count)=>{
that.arr.push({
class:"center",
info:data +" 離開控制檯"
});
that.count = count;
});
},
usedClick:debounce((txt)=>{
vm.text = txt;
vm.func(1);
vm.$refs.popup.close();
})
},
watch:{
arr:{
handler(newVal,oldVal){
this.$nextTick(function(){
uni.createSelectorQuery().select(".gundong").boundingClientRect((res)=>{
let newbottom = res.bottom;
if(Number(newbottom) > Number(this.oldbottom)){
this.scrollTop = newbottom
}
this.oldbottom = newbottom
}).exec();
});
},
deep:true
}
},
components:{
uniPopup
}
}
</script>
<style lang="less">
.chat{
background: #f5f5f5;position: relative;
.online{
background: #fff;height: 60rpx;line-height: 60rpx;text-align: center;color: #fd7633;font-size: 24rpx;font-weight: bold;
}
.chat-main{
position: absolute;width: 100%;top: 60rpx;bottom: 100rpx;overflow-y: auto;border-top: 1px solid #fd7633;border-bottom: 1px solid #fd7633;padding: 0 20rpx 10rpx;box-sizing: border-box;
.gundong{
width: 100%;height: 1px;
}
.item{
padding-top: 10rpx;
}
.info{
font-size: 24rpx;color: #aaa;text-align: center;
}
.user-icon{
width: 60rpx;height: 60rpx;border-radius: 8rpx;overflow: hidden;
.user-img{
width: 100%;height: 100%;
}
}
.user-info{
max-width: 500rpx;border-radius: 8rpx;background: #fd7633;color: #333;font-size: 28rpx;padding: 10rpx;box-sizing: border-box;line-height: 40rpx;
}
.left{
display: flex;position: relative;
.left-icon{
width: 0;height: 0;border: 10rpx solid;border-color: transparent #fff transparent transparent;margin-top: 17.5rpx;
}
.user-info{
background: #fff;
}
}
.right{
display: flex;justify-content: flex-end;position: relative;
.right-icon{
width: 0;height: 0;border: 10rpx solid;border-color: transparent transparent transparent #fd7633;margin-top: 17.5rpx;
}
}
}
.send{
width:100%;height: 100rpx;background: #fff;position: absolute;bottom: 0;padding: 0 40rpx;box-sizing: border-box;
.send-box{
width: 100%;height: 80rpx;line-height: 80rpx;margin-top: 10rpx;display: flex;align-items: center;
.input{
flex: 1;padding:0 10rpx;height: 100%;color: #333;font-size: 28rpx;background: #eee;border-radius: 8rpx;
}
.img{
width: 60rpx;height: 60rpx;margin: 0 10rpx;
.icon{
width: 100%;height: 100%;
}
}
}
}
.used-list{
background: #fff;border-radius: 8rpx 8rpx 0 0;padding: 20rpx 10rpx;
.used-item{
width: 100%;paddin-bottom: 10rpx;font-size: 28rpx;color: #fd7633;line-height: 50rpx;
}
}
}
</style>
3、main.js
import Vue from 'vue'
import App from './App'
let io = require("@/common/weapp.socket.io.js");
let socket = io('http://localhost:8082');
Vue.config.productionTip = false
Vue.prototype.socket = socket;
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()