詳解微信小程序開發(fā)聊天室—實(shí)時(shí)聊天,支持圖片預(yù)覽
第一次寫小程序,老板就讓我用websoket寫個(gè)聊天對(duì)話,群聊這種。第一次寫聊天功能,第一次用websoket,第一次用小程序,這是在考驗(yàn)我嗎?不過(guò)我還是研究了一下,終于實(shí)現(xiàn)了。
首先看一下界面,界面很簡(jiǎn)單,就是首頁(yè)剛進(jìn)來(lái)獲取了用戶信息頭像,昵稱等。點(diǎn)擊進(jìn)入聊天室就可以聊天了,下面我介紹的是前端代碼實(shí)現(xiàn),后臺(tái)需要做的很簡(jiǎn)單,就是你給他發(fā)送什么數(shù)據(jù),他就給你返回什么數(shù)據(jù),就是在接收前臺(tái)發(fā)送過(guò)來(lái)的圖片的時(shí)候需要做個(gè)格式轉(zhuǎn)換,因?yàn)橛袝r(shí)候前端將接收到的json字符串轉(zhuǎn)換json對(duì)象的時(shí)候,遇到有特殊的標(biāo)點(diǎn)符號(hào)可能會(huì)報(bào)錯(cuò),比如我就是‘\'報(bào)的錯(cuò),找了半天。
因?yàn)橛腥俗稍?,所以附上所有小程序代碼,地址:https://github.com/chongwenwen/weixin_chat/tree/master
有人說(shuō)為什么沒(méi)有utile.js的代碼,這個(gè)功能只用到websoket.js跟utile.js沒(méi)有關(guān)系哦!還有后臺(tái)代碼在頁(yè)面最底下
文檔目錄結(jié)構(gòu)如下:(聊天頁(yè)面為chat)
chat.wxml頁(yè)面
首先寫好頁(yè)面結(jié)構(gòu),既然是群聊功能,肯定有自己和別人,所以頁(yè)面的view盒子應(yīng)給有兩部分,一個(gè)內(nèi)容左側(cè)顯示,一個(gè)內(nèi)容右側(cè)顯示,下面是代碼,因?yàn)闆](méi)有正式注冊(cè)企業(yè)項(xiàng)目,我用的服務(wù)器都是本地的服務(wù)器,所以界面區(qū)分別人和我的聊天信息是用昵稱區(qū)分的,如果正式項(xiàng)目應(yīng)該是由一個(gè)用戶標(biāo)記去區(qū)分的
<view class="news" bindtap='outbtn'> <view class="chat-notice" wx:if="{{userInfo}}">系統(tǒng)消息: 歡迎 {{ 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}}" data-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}}" data-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="請(qǐng)輸入聊天內(nèi)容.." value="{{massage}}" bindinput='bindChange'></input> <view class="add" bindtap='increase'>+</view> <button type="primary" bindtap='send' formType="reset" size="small" button-hover="blue">發(fā)送</button> </form> <view class='increased {{aniStyle?"slideup":"slidedown"}}' wx:if="{{increase}}"> <view class="image" bindtap='chooseImage'>相冊(cè) </view> </view> </view>
websoket.js文件
在utils目錄下,是封裝了websoket的請(qǐng)求過(guò)程,以便在chat.js中調(diào)用。需要注意的是wx.connectSocket代表客戶端首次和服務(wù)器建立聯(lián)系,wx.onSocketOpen才是正式打開通道,wx.onSocketMessage必須在 wx.onSocketOpen 回調(diào)之后發(fā)送才生效。
wx.onSocketMessage里面帶有參數(shù)是一個(gè)函數(shù)回調(diào),這個(gè)回調(diào)就是后臺(tái)服務(wù)器實(shí)時(shí)接收并返給前臺(tái)的數(shù)據(jù)
var url = 'ws://........';//服務(wù)器地址 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 }) //接受服務(wù)器消息 wx.onSocketMessage(func);//func回調(diào)可以拿到服務(wù)器返回的數(shù)據(jù) }); wx.onSocketError(function (res) { wx.showToast({ title: '信道連接失敗,請(qǐng)檢查!', icon: "none", duration: 2000 }) }) } //發(fā)送消息 function send(msg) { wx.sendSocketMessage({ data: msg }); } module.exports = { connect: connect, send: send }
chat.js文件
聊天室的邏輯操作頁(yè)面,websocket.connect(){}調(diào)用的是websocket.js封裝好的websoket的邏輯函數(shù),回調(diào)就是后臺(tái)的數(shù)據(jù),之所以在本頁(yè)面調(diào)用就是方便接收以后的邏輯操作。我建立文件的時(shí)候用的就是微信官方的快速模板生成的,所以app.js里面沒(méi)有變動(dòng),用戶在chat.js獲取userInfo的時(shí)候可以引用全局的app.globalData.userInfo
還有要注意的一點(diǎn)就是在選擇發(fā)送圖片的時(shí)候,必須是先把本地的圖片地址發(fā)送給后臺(tái),轉(zhuǎn)換成服務(wù)器的圖片地址再次通過(guò)wensoket.send發(fā)送給服務(wù)器,這個(gè)時(shí)候服務(wù)器推送給其他用戶的才是正確的地址,否則你的本地地址其他用戶是訪問(wèn)不到的。
// pages/chat/chat.js const app = getApp() var websocket = require('../../utils/websocket.js'); var utils = require('../../utils/util.js'); Page({ /** * 頁(yè)面的初始數(shù)據(jù) */ data: { newslist:[], userInfo: {}, scrollTop: 0, increase:false,//圖片添加區(qū)域隱藏 aniStyle: true,//動(dòng)畫效果 message:"", previewImgList:[] }, /** * 生命周期函數(shù)--監(jiān)聽頁(yè)面加載 */ onLoad: function () { var that = this if (app.globalData.userInfo) { this.setData({ userInfo: app.globalData.userInfo }) } //調(diào)通接口 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 }) }) }, // 頁(yè)面卸載 onUnload(){ wx.closeSocket(); wx.showToast({ title: '連接已斷開~', icon: "none", duration: 2000 }) }, //事件處理函數(shù) 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() } }, //監(jiān)聽input值的改變 bindChange(res) { this.setData({ message : res.detail.value }) }, cleanInput(){ //button會(huì)自動(dòng)清空,所以不能再次清空而是應(yīng)該給他設(shè)置目前的input值 this.setData({ message: this.data.message }) }, increase() { this.setData({ increase: true, aniStyle: true }) }, //點(diǎn)擊空白隱藏message下選框 outbtn(){ this.setData({ increase: false, aniStyle: true }) }, //發(fā)送圖片 chooseImage() { var that = this wx.chooseImage({ count: 1, // 默認(rèn)9 sizeType: ['original', 'compressed'], // 可以指定是原圖還是壓縮圖,默認(rèn)二者都有 sourceType: ['album', 'camera'], // 可以指定來(lái)源是相冊(cè)還是相機(jī),默認(rèn)二者都有 success: function (res) { // 返回選定照片的本地文件路徑列表,tempFilePath可以作為img標(biāo)簽的src屬性顯示圖片 var tempFilePaths = res.tempFilePaths // console.log(tempFilePaths) wx.uploadFile({ url: 'http://.....', //服務(wù)器地址 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() } } }) } }) }, //圖片預(yù)覽 previewImg(e){ var that = this //必須給對(duì)應(yīng)的wxml的image標(biāo)簽設(shè)置data-set=“圖片路徑”,否則接收不到 var res = e.target.dataset.src var list = this.data.previewImgList //頁(yè)面的圖片集合數(shù)組 //判斷res在數(shù)組中是否存在,不存在則push到數(shù)組中, -1表示res不存在 if (list.indexOf(res) == -1 ) { this.data.previewImgList.push(res) } wx.previewImage({ current: res, // 當(dāng)前顯示圖片的http鏈接 urls: that.data.previewImgList // 需要預(yù)覽的圖片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節(jié)點(diǎn)的下邊界坐標(biāo) }) res[1].scrollTop // 顯示區(qū)域的豎直滾動(dòng)位置 }) }, })
最后是頁(yè)面的樣式文件chat.wxss
/* pages/chat/chat.wxss */ page { background-color: #f7f7f7; height: 100%; } /* 聊天內(nèi)容 */ .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; } /* 信息輸入?yún)^(qū)域 */ .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); } }
后臺(tái)代碼(圖片):
以上所述是小編給大家介紹的微信小程序?qū)崟r(shí)聊天支持圖片預(yù)覽詳解整合,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
前端用echarts繪制含有多個(gè)分層的波形圖關(guān)鍵代碼
每次實(shí)現(xiàn)各種圖表時(shí),總會(huì)用到echarts,不得不說(shuō)確實(shí)是一個(gè)非常好用的開源庫(kù),這篇文章主要給大家介紹了關(guān)于前端用echarts繪制含有多個(gè)分層的波形圖的相關(guān)資料,需要的朋友可以參考下2024-03-03H5實(shí)現(xiàn)中獎(jiǎng)記錄逐行滾動(dòng)切換效果
這篇文章主要為大家詳細(xì)介紹了H5實(shí)現(xiàn)中獎(jiǎng)記錄逐行滾動(dòng)切換效果,利用定時(shí)器實(shí)現(xiàn)中獎(jiǎng)記錄逐行展示,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03Ant Design Pro 下實(shí)現(xiàn)文件下載的實(shí)現(xiàn)代碼
這篇文章主要介紹了Ant Design Pro 下實(shí)現(xiàn)文件下載的實(shí)現(xiàn)代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12javascript實(shí)現(xiàn)一款好看的秒表計(jì)時(shí)器
這篇文章主要為大家詳細(xì)介紹了一款好看的秒表計(jì)時(shí)器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-09-095個(gè)javascript的數(shù)字格式化函數(shù)分享
Javascript沒(méi)有任何內(nèi)建的格式化函數(shù),這里我們通過(guò)Google收集了5個(gè)javascript的數(shù)字格式化函數(shù),希望對(duì)于大家的web開發(fā)能夠帶來(lái)方便2011-12-12