第一次寫小程式,老闆就讓我用websoket寫個聊天對話,群聊這種。第一次寫聊天功能,第一次用websoket,第一次用小程式,這是在考驗我嗎?
不過我還是研究了一下,終于實作了。
首先看一下界面,界面很簡單,就是首頁剛進來擷取了使用者資訊頭像,昵稱等。點選進入聊天室就可以聊天了,下面我介紹的是前端代碼實作,背景需要做的很簡單,就是你給他發送什麼資料,他就給你傳回什麼資料,就是在接收前台發送過來的圖檔的時候需要做個格式轉換,因為有時候前端将接收到的json字元串轉換json對象的時候,遇到有特殊的标點符号可能會報錯,比如我就是‘\’報的錯,找了半天。
因為有人咨詢,是以附上所有小程式代碼,位址:https://github.com/chongwenwen/weixin_chat/tree/master
有人說為什麼沒有utile.js的代碼,這個功能隻用到websoket.js跟utile.js沒有關系哦!還有背景代碼在頁面最底下
文檔目錄結構如下:(聊天頁面為chat)
chat.wxml頁面
首先寫好頁面結構,既然是群聊功能,肯定有自己和别人,是以頁面的view盒子應給有兩部分,一個内容左側顯示,一個内容右側顯示,下面是代碼,因為沒有正式注冊企業項目,我用的伺服器都是本地的伺服器,是以界面區分别人和我的聊天資訊是用昵稱區分的,如果正式項目應該是由一個使用者标記去區分的
<view class="news" bindtap='outbtn'>
<view class="chat-notice" wx:if="{{userInfo}}">系統消息: 歡迎 {{ userInfo.nickName }} 加入群聊</view>
<view class="historycon">
<scroll-view scroll-y="true" class="history" scroll-top="{{scrollTop}}">
<block wx:for="{{newslist}}" wx:key>
<!-- 曆史消息 -->
<!-- <view class="chat-news">
<view style="text-align: left;padding-left: 20rpx;">
<image class='new_img' src="{{item.avatarUrl? item.avatarUrl:'images/avator.png'}}"></image>
<text class="name">{{ item.nickName }}{{item.date}}</text>
</view>
<view class='you_left'>
<block wx:if="{{item.type=='text'}}">
<view class='new_txt'>{{item.content}}</view>
</block>
<block wx:if="{{item.type=='image'}}">
<image class="selectImg" src="{{item.images}}"></image>
</block>
</view>
</view> -->
<view>{{item.date}}</view>
<!--自己的消息 -->
<view class="chat-news" wx:if="{{item.nickName == userInfo.nickName}}">
<view style="text-align: right;padding-right: 20rpx;">
<text class="name">{{ item.nickName }}</text>
<image class='new_img' src="{{userInfo.avatarUrl}}"></image>
</view>
<view class='my_right'>
<block wx:if="{{item.type=='text'}}">
<view class='new_txt'>{{item.content}}</view>
</block>
<block wx:if="{{item.type=='image'}}">
<image class="selectImg" src="{{item.images}}" src="{{item.images}}" lazy-load="true" bindtap="previewImg"></image>
</block>
</view>
</view>
<!-- 别人的消息 -->
<view class="chat-news" wx:else>
<view style="text-align: left;padding-left: 20rpx;">
<image class='new_img' src="{{item.avatarUrl? item.avatarUrl:'images/avator.png'}}"></image>
<text class="name">{{ item.nickName }}</text>
</view>
<view class='you_left'>
<block wx:if="{{item.type=='text'}}">
<view class='new_txt'>{{item.content}}</view>
</block>
<block wx:if="{{item.type=='image'}}">
<image class="selectImg" src="{{item.images}}" src="{{item.images}}" lazy-load="true" bindtap="previewImg"></image>
</block>
</view>
</view>
</block>
</scroll-view>
</view>
</view>
<view id="flag"></view>
<!-- 聊天輸入 -->
<view class="message">
<form bindreset="cleanInput" class="sendMessage">
<input type="text" placeholder="請輸入聊天内容.." value="{{massage}}" bindinput='bindChange'></input>
<view class="add" bindtap='increase'>+</view>
<button type="primary" bindtap='send' formType="reset" size="small" button-hover="blue">發送</button>
</form>
<view class='increased {{aniStyle?"slideup":"slidedown"}}' wx:if="{{increase}}">
<view class="image" bindtap='chooseImage'>相冊 </view>
</view>
</view>
websoket.js檔案
在utils目錄下,是封裝了websoket的請求過程,以便在chat.js中調用。需要注意的是wx.connectSocket代表用戶端首次和伺服器建立聯系,wx.onSocketOpen才是正式打開通道,wx.onSocketMessage必須在 wx.onSocketOpen 回調之後發送才生效。
wx.onSocketMessage裡面帶有參數是一個函數回調,這個回調就是背景伺服器實時接收并返給前台的資料
var url = 'ws://........';//伺服器位址
function connect(user,func) {
wx.connectSocket({
url: url,
header:{'content-type': 'application/json'},
success: function () {
console.log('信道連接配接成功~')
},
fail: function () {
console.log('信道連接配接失敗~')
}
})
wx.onSocketOpen(function (res) {
wx.showToast({
title: '信道已開通~',
icon: "success",
duration: 2000
})
//接受伺服器消息
wx.onSocketMessage(func);//func回調可以拿到伺服器傳回的資料
});
wx.onSocketError(function (res) {
wx.showToast({
title: '信道連接配接失敗,請檢查!',
icon: "none",
duration: 2000
})
})
}
//發送消息
function send(msg) {
wx.sendSocketMessage({
data: msg
});
}
module.exports = {
connect: connect,
send: send
}
chat.js檔案
聊天室的邏輯操作頁面,websocket.connect(){}調用的是websocket.js封裝好的websoket的邏輯函數,回調就是背景的資料,之是以在本頁面調用就是友善接收以後的邏輯操作。我建立檔案的時候用的就是微信官方的快速模闆生成的,是以app.js裡面沒有變動,使用者在chat.js擷取userInfo的時候可以引用全局的app.globalData.userInfo
還有要注意的一點就是在選擇發送圖檔的時候,必須是先把本地的圖檔位址發送給背景,轉換成伺服器的圖檔位址再次通過wensoket.send發送給伺服器,這個時候伺服器推送給其他使用者的才是正确的位址,否則你的本地位址其他使用者是通路不到的。
// pages/chat/chat.js
const app = getApp()
var websocket = require('../../utils/websocket.js');
var utils = require('../../utils/util.js');
Page({
/**
* 頁面的初始資料
*/
data: {
newslist:[],
userInfo: {},
scrollTop: 0,
increase:false,//圖檔添加區域隐藏
aniStyle: true,//動畫效果
message:"",
previewImgList:[]
},
/**
* 生命周期函數--監聽頁面加載
*/
onLoad: function () {
var that = this
if (app.globalData.userInfo) {
this.setData({
userInfo: app.globalData.userInfo
})
}
//調通接口
websocket.connect(this.data.userInfo, function (res) {
// console.log(JSON.parse(res.data))
var list = []
list = that.data.newslist
list.push(JSON.parse(res.data))
that.setData({
newslist: list
})
})
},
// 頁面解除安裝
onUnload(){
wx.closeSocket();
wx.showToast({
title: '連接配接已斷開~',
icon: "none",
duration: 2000
})
},
//事件處理函數
send: function () {
var flag = this
if (this.data.message.trim() == ""){
wx.showToast({
title: '消息不能為空哦~',
icon: "none",
duration: 2000
})
}else {
setTimeout(function(){
flag.setData({
increase: false
})
},500)
websocket.send('{ "content": "' + this.data.message + '", "date": "' + utils.formatTime(new Date()) + '","type":"text", "nickName": "' + this.data.userInfo.nickName + '", "avatarUrl": "' + this.data.userInfo.avatarUrl+'" }')
this.bottom()
}
},
//監聽input值的改變
bindChange(res) {
this.setData({
message : res.detail.value
})
},
cleanInput(){
//button會自動清空,是以不能再次清空而是應該給他設定目前的input值
this.setData({
message: this.data.message
})
},
increase() {
this.setData({
increase: true,
aniStyle: true
})
},
//點選空白隐藏message下選框
outbtn(){
this.setData({
increase: false,
aniStyle: true
})
},
//發送圖檔
chooseImage() {
var that = this
wx.chooseImage({
count: 1, // 預設9
sizeType: ['original', 'compressed'], // 可以指定是原圖還是壓縮圖,預設二者都有
sourceType: ['album', 'camera'], // 可以指定來源是相冊還是相機,預設二者都有
success: function (res) {
// 傳回標明照片的本地檔案路徑清單,tempFilePath可以作為img标簽的src屬性顯示圖檔
var tempFilePaths = res.tempFilePaths
// console.log(tempFilePaths)
wx.uploadFile({
url: 'http://.....', //伺服器位址
filePath: tempFilePaths[0],
name: 'file',
headers: {
'Content-Type': 'form-data'
},
success: function (res) {
if (res.data){
that.setData({
increase: false
})
websocket.send('{"images":"'+ res.data +'","date":"'+utils.formatTime(new Date())+'","type":"image","nickName":"'+that.data.userInfo.nickName+'","avatarUrl":"'+that.data.userInfo.avatarUrl+'"}')
that.bottom()
}
}
})
}
})
},
//圖檔預覽
previewImg(e){
var that = this
//必須給對應的wxml的image标簽設定data-set=“圖檔路徑”,否則接收不到
var res = e.target.dataset.src
var list = this.data.previewImgList //頁面的圖檔集合數組
//判斷res在數組中是否存在,不存在則push到數組中, -1表示res不存在
if (list.indexOf(res) == -1 ) {
this.data.previewImgList.push(res)
}
wx.previewImage({
current: res, // 目前顯示圖檔的http連結
urls: that.data.previewImgList // 需要預覽的圖檔http連結清單
})
},
//聊天消息始終顯示最底端
bottom: function () {
var query = wx.createSelectorQuery()
query.select('#flag').boundingClientRect()
query.selectViewport().scrollOffset()
query.exec(function (res) {
wx.pageScrollTo({
scrollTop: res[0].bottom // #the-id節點的下邊界坐标
})
res[1].scrollTop // 顯示區域的豎直滾動位置
})
},
})
最後是頁面的樣式檔案chat.wxss
/* pages/chat/chat.wxss */
page {
background-color: #f7f7f7;
height: 100%;
}
/* 聊天内容 */
.news {
padding-top: 30rpx;
text-align: center;
/* height:100%; */
box-sizing:border-box;
}
#flag{
margin-bottom: 100rpx;
height: 30rpx;
}
.chat-notice{
text-align: center;
font-size: 30rpx;
padding: 10rpx 0;
color: #666;
}
.historycon {
height: 100%;
width: 100%;
/* flex-direction: column; */
display: flex;
border-top: 0px;
}
/* 聊天 */
.chat-news{
width: 100%;
overflow: hidden;
}
.chat-news .my_right {
float: right;
/* right: 40rpx; */
padding: 10rpx 10rpx;
}
.chat-news .name{
margin-right: 10rpx;
}
.chat-news .you_left {
float: left;
/* left: 5rpx; */
padding: 10rpx 10rpx;
}
.selectImg{
display: inline-block;
width: 150rpx;
height: 150rpx;
margin-left: 50rpx;
}
.my_right .selectImg{
margin-right: 80rpx;
}
.new_img {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
vertical-align: middle;
margin-right: 10rpx;
}
.new_txt {
max-width: 300rpx;
display: inline-block;
border-radius: 6rpx;
line-height: 60rpx;
background-color: #95d4ff;
padding: 5rpx 20rpx;
margin: 0 10rpx;
margin-left: 50rpx;
}
.my_right .new_txt{
margin-right:60rpx;
}
.you{
background-color: lightgreen;
}
.my {
border-color: transparent transparent transparent #95d4ff;
}
.you {
border-color: transparent #95d4ff transparent transparent;
}
.hei{
margin-top: 50px;
height: 20rpx;
}
.history {
height: 100%;
margin-top: 15px;
padding: 10rpx;
font-size: 14px;
line-height: 40px;
word-break: break-all;
}
::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
z-index: -1;
}
/* 資訊輸入區域 */
.message{
position: fixed;
bottom:0;
width: 100%;
}
.sendMessage{
display: block;
height: 80rpx;
padding: 10rpx 10rpx;
background-color: #fff;
border-top: 2rpx solid #eee;
border-bottom: 2rpx solid #eee;
z-index:3;
}
.sendMessage input{
float:left;
width: 66%;
height: 100%;
line-height: 80rpx;
border-bottom: 1rpx solid #ccc;
padding:0 10rpx;
font-size: 35rpx;
color: #666;
}
.sendMessage view{
display: inline-block;
width: 80rpx;
height: 80rpx;
line-height: 80rpx;
font-size: 60rpx;
text-align: center;
color: #999;
border: 1rpx solid #ccc;
border-radius: 50%;
margin-left: 10rpx;
}
.sendMessage button {
float: right;
font-size: 35rpx;
}
.increased{
width:100%;
/* height: 150rpx; */
padding: 40rpx 30rpx;
background-color: #fff;
}
.increased .image{
width: 100rpx;
height: 100rpx;
border: 3rpx solid #ccc;
line-height: 100rpx;
text-align: center;
border-radius: 8rpx;
font-size:35rpx;
}
@keyframes slidedown {
from {
transform: translateY(0);
}
to {
transform: translateY(100%);
}
}
.slidedown {
animation: slidedown 0.5s linear ;
}
.slideup {
animation: slideup 0.5s linear ;
}
@keyframes slideup {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
背景代碼(圖檔):