微信小程序本身自带列表的渲染,但是存在着一些问题:
不能局部刷新,每次更新就会刷新全部数据,在性能上会有一些影响数据量很大时候,渲染会出现卡顿现象,比如一百条以上,分页几十页针对这种问题,微信用插件的方式推出了recycle-view,下面主要对recycle-view的使用方式及其使用过程中遇到的一些问题进行描述。但是recycle-view不是很完善,相对于Androiod的RecycleView来说依然有着很多限制。
在这里使用npm进行集成,npm的配置方式参考该链接: https://blog.csdn.net/Mr_Tony/article/details/107735467
recycle-view的集成方式参考该链接: https://developers.weixin.qq.com/miniprogram/dev/extended/component-plus/recycle-view.html
msgSendStatus.js
//消息发送状态类型 const SEND_LOADING = 0;//发送中 const SEND_SUCCESS = 1;//发送成功 const SEND_FAIL = 2;//发送失败 export{ SEND_LOADING, SEND_SUCCESS, SEND_FAIL }msgType.js
//聊天类型 const TEXT = 0; const PHOTO = 1; export{ TEXT, PHOTO }util.js
const formatTime = date => { const year = date.getFullYear() const month = date.getMonth() + 1 const day = date.getDate() const hour = date.getHours() const minute = date.getMinutes() const second = date.getSeconds() return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') } /** * 将Json转为form表单格式进行提交 */ function JSON_to_URLEncoded(element,key,list){ var list = list || []; if(typeof(element)=="object"){ for (var idx in element) this.JSON_to_URLEncoded(element[idx],key?key+"["+idx+"]":idx,list); } else { list.push(key+"="+encodeURIComponent(element)); } return list.join("&"); } const formatNumber = n => { n = n.toString() return n[1] ? n : '0' + n } const getFileSuffix = fileName =>{ return fileName.slice(fileName.lastIndexOf(".")) } module.exports = { formatTime: formatTime, formatNumber: formatNumber, JSON_to_URLEncoded, getFileSuffix }fileUtils.js
/** * 生成详情Banner文件名字 * @param fileSuffix 文件后缀 */ const getDetailsFileName = (fileSuffix) => { return "goods_details_"+Date.now()+fileSuffix } /** * 生成详情Banner文件名字 * @param fileSuffix 文件后缀 */ const getLogoFileName = (fileSuffix) => { return "goods_logo_"+Date.now()+fileSuffix } module.exports = { getDetailsFileName, getLogoFileName }js和xml中工具类无法通用
chat_util.wxs
/** * 主要是用来处理聊天页面逻辑 */ var durtionTime = 180000 //三分钟 var foo = "'hello world' from tools.wxs"; var bar = function (d) { return d; } /** * 判断消息朝向 */ var isSelf = function (sendUser, selfUser) { return sendUser == selfUser } var showTime = function (sendTime) { let result = "" let currentTime = getDate().getTime() if (currentTime - sendTime < durtionTime) { result = "" } else { result = "三分钟前" } return result } /** * * @param timestamp 时间戳类型 */ var dateDiff = function (timestamp) { // 补全为13位 var arrTimestamp = (timestamp + '').split(''); for (var start = 0; start < 13; start++) { if (!arrTimestamp[start]) { arrTimestamp[start] = '0'; } } timestamp = arrTimestamp.join('') * 1; var minute = 1000 * 60; var hour = minute * 60; var day = hour * 24; var halfamonth = day * 15; var month = day * 30; var now = getDate().getTime(); var diffValue = now - timestamp; // 如果本地时间反而小于变量时间 if (diffValue < 0) { return '不久前'; } // 计算差异时间的量级 var monthC = diffValue / month; var weekC = diffValue / (7 * day); var dayC = diffValue / day; var hourC = diffValue / hour; var minC = diffValue / minute; // 数值补0方法 var zero = function (value) { if (value < 10) { return '0' + value; } return value; }; // 使用 if (monthC > 12) { // 超过1年,直接显示年月日 return (function () { var date = getDate(timestamp); return date.getFullYear() + '年' + zero(date.getMonth() + 1) + '月' + zero(date.getDate()) + '日'; })(); } else if (monthC >= 1) { return parseInt(monthC) + "月前"; } else if (weekC >= 1) { return parseInt(weekC) + "周前"; } else if (dayC >= 1) { return parseInt(dayC) + "天前"; } else if (hourC >= 1) { return parseInt(hourC) + "小时前"; } else if (minC >= 1) { return parseInt(minC) + "分钟前"; } return '刚刚'; }; /** * * @param dateStr 时间格式为 2016-12-12 12:20:00 */ function getDateDiff(dateStr) { var publishTime = getDateTimeStamp(dateStr) / 1000, d_seconds, d_minutes, d_hours, d_days, timeNow = parseInt(getDate().getTime() / 1000), d, date = getDate(publishTime * 1000), Y = date.getFullYear(), M = date.getMonth() + 1, D = date.getDate(), H = date.getHours(), m = date.getMinutes(), s = date.getSeconds(); //小于10的在前面补0 if (M < 10) { M = '0' + M; } if (D < 10) { D = '0' + D; } if (H < 10) { H = '0' + H; } if (m < 10) { m = '0' + m; } if (s < 10) { s = '0' + s; } d = timeNow - publishTime; d_days = parseInt(d / 86400); d_hours = parseInt(d / 3600); d_minutes = parseInt(d / 60); d_seconds = parseInt(d); if (d_days > 0 && d_days < 3) { return d_days + '天前'; } else if (d_days <= 0 && d_hours > 0) { return d_hours + '小时前'; } else if (d_hours <= 0 && d_minutes > 0) { return d_minutes + '分钟前'; } else if (d_seconds < 60) { if (d_seconds <= 0) { return '刚刚'; } else { return d_seconds + '秒前'; } } else if (d_days >= 3 && d_days < 30) { return M + '-' + D + ' ' + H + ':' + m; } else if (d_days >= 30) { return Y + '-' + M + '-' + D + ' ' + H + ':' + m; } } module.exports = { FOO: foo, bar: bar, isSelf: isSelf, showTime: showTime, dateDiff: dateDiff, }; module.exports.msg = "some msg";templete_chatitem.wxml
<!--pages/template/chat/templete_chatitem.wxml--> <!--文本布局--> <template name="chatText"> <view> <view class="chat-item {{self ? 'self' : ''}}"> <image class="avatar chat-border" src="{{avater}}" lazy-load="true"> </image> <view class="content-text {{self ? 'right-text' : 'left-text'}}">{{content}}</view> <template is="chatStatus" data="{{sendStatus}}"></template> </view> <view class="chatTime" hidden="{{!showTime}}"> {{showTime}} </view> </view> </template> <!-- 图片布局 --> <template name="chatPhoto"> <view> <view class="chat-item {{self ? 'self' : ''}}"> <image class="avatar chat-border" src="{{avater}}" lazy-load="true"> </image> <image class="content-photo" src="{{content}}" lazy-load="true"></image> <template is="chatStatus" data="{{sendStatus}}"></template> </view> </view> <view class="chatTime" hidden="{{!showTime}}"> {{showTime}} </view> </template> <!-- 系统文本布局 --> <template name="chatSystemText"> <view class="chat-item-system"> <view class="system-text">{{content}}</view> </view> </template> <template name="chatStatus"> <!--消息发送中--> <image wx:if="{{sendStatus == 0}}" class="send-status" src="http://148.70.194.24:8086/file/fileDown?saveName=2e5077937c0844a09f25e9c4c087c048.gif"></image> <!--消息发送失败--> <image wx:elif="{{sendStatus == 2}}" class="send-status" src="http://148.70.194.24:8086/file/fileDown?saveName=957e88f4c71845aab2dd787ba49d0619.png"></image> </template>templete_chatitem.wxss
/* pages/template/chat/templete_chatitem.wxss */ .chat-border{ border-radius: 10rpx; } .self{ flex-direction: row-reverse; } .chat-item{ display: flex; padding: 30rpx; } .chatTime{ display: flex; justify-content: center; color: gray; font-size: 30rpx; } .avatar{ width: 100rpx; height: 100rpx; margin-right: 10rpx; flex-shrink: 0; } .send-status{ width: 50rpx; height: 50rpx; } .chat-item view{ background-color: #fff; padding: 5px 8px; display: inline-block; border-radius: 4px; margin:10rpx 30rpx 30rpx 30rpx; position: relative; } .left-text::after{ content: ''; border: 8px solid #ffffff00; border-right: 8px solid #fff; position: absolute; top: 6px; left: -16px; } .right-text::after{ content: ''; border: 8px solid #ffffff00; border-left: 8px solid #fff; position: absolute; top: 6px; right: -16px; } .content-text{ color: #E0441A; font-size: 32rpx; word-wrap: break-word; word-break: break-all; white-space: pre-line; margin: 100rpx; } .content-photo{ max-width: 400rpx; max-height: 300rpx; margin: 0 30rpx; } .chat-item-system{ display: flex; justify-content: center; width: 100%; } .system-text{ display: flex; max-width: 500rpx; font-size: 28rpx; color: white; word-wrap: break-word; word-break: break-all; white-space: pre-line; background-color: gray; opacity: 0.8; display: flex; padding: 20rpx; border-radius: 20rpx; }chat.json
{ "usingComponents": { "recycle-view": "miniprogram-recycle-view/recycle-view", "recycle-item": "miniprogram-recycle-view/recycle-item" }, "navigationBarTitleText": "客服聊天", "enablePullDownRefresh": false }chat.js
// pages/chat/chat.js import * as sendStatus from './msgSendStatus' const util = require("../../utils/util.js") const fileUtil = require("../../utils/fileUtils.js") const chatMessageUtil = require("./chat_message_util") const createRecycleContext = require('miniprogram-recycle-view')//无限列表 const app = getApp() var recycleView const rpx2px = (rpx) => (rpx / 750) * wx.getSystemInfoSync().windowWidth var localMessageId = 0;//每次本地消息id+1 Page({ /** * 页面的初始数据 */ init: { tempFileList: [ // { // localPath: "",//图片在本地的地址 // netPath: "",//图片的网络地址 // saveName: "",//图片保存的名字 // localMessageId: localMessageId,//该消息的临时消息Id // } ],//本地图片列表 commonMessage: {},//通用消息结构 messageText: "",//输入的文本内容 avater: "https://ossweb-img.qq.com/images/lol/web201310/skin/big143004.jpg", friendId:"1"//好友的用户Id }, data: { userId: 1, chatContentHeight:440,//中间聊天内容的布局 ispull:true,//是否开启下拉刷新 isstoppull:true,//是否停止下拉刷新 false:停止,true:不停止 chatList: [ { msgId: 0, msgType: 0, avater: "https://ossweb-img.qq.com/images/lol/web201310/skin/big143004.jpg", content: "这是文本内容", fromId: 2, toId: 1, sendTime: 1597073074000, sendStatus:1 }, { msgId: 1, msgType: 0, avater: "https://ossweb-img.qq.com/images/lol/web201310/skin/big143004.jpg", content: "这是文本内容", fromId: 1, toId: 2, sendTime: 1597505074000, sendStatus:1 }, { msgId: 2, msgType: 1, avater: "https://ossweb-img.qq.com/images/lol/web201310/skin/big143004.jpg", content: "https://ossweb-img.qq.com/images/lol/web201310/skin/big143004.jpg", fromId: 1, toId: 2, sendTime: 1597505074000, sendStatus:1 }, { msgId: 2, msgType: 2, avater: "https://ossweb-img.qq.com/images/lol/web201310/skin/big143004.jpg", content: "这是一条系统消息这是一条系统消息这是一条系统消息这是一条系统消息", fromId: 1, toId: 2, sendTime: 1597505074000, sendStatus:1 } ], selectIndex: 0 }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { let friendId = options.userId this.init.friendId = friendId let userInfo = app.globalData.userInfo avater = userInfo.avatarUrl this.init.commonMessage = chatMessageUtil.createCommonMessage(userInfo.id, friendId, avater) }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady: function () { recycleView = createRecycleContext({ id: 'recycleId', dataKey: 'chatList', page: this, // itemSize: { // 这个参数也可以直接传下面定义的this.itemSizeFunc函数 // height: rpx2px(220), // width: rpx2px(750) // } itemSize:this.itemSizeFunc }) // this.cacluteWidthAndHeight() recycleView.append(this.data.chatList) // this.cacluteChatContentHeight() }, /** * 动态计算每一个item的宽高 * @param {object} item 当前item显示的内容 * @param {Integer} idx */ itemSizeFunc: function (item, idx) { return { width: rpx2px(220), height: rpx2px(400) } }, cacluteWidthAndHeight: function(){ let id = ".text" var obj=wx.createSelectorQuery(); obj.selectAll(id).boundingClientRect(); obj.exec(function (rect) { console.log("YM---->渲染区域信息:",rect) }) ; }, /** * 计算聊天内容的整体页面高度 */ cacluteChatContentHeight: function(){ let that = this let windowHeight = wx.getSystemInfoSync().windowHeight let id = ".chat_bottom" var obj=wx.createSelectorQuery(); obj.selectAll(id).boundingClientRect(); obj.exec(function (rect) { // console.log(rect[0].height) // console.log(rect[0].width) let bottomOperatorHeight = rect[0][0].height let chatContentHeight = windowHeight - bottomOperatorHeight that.setData({ chatContentHeight:chatContentHeight }) }) ; }, /** * * @param {*} e 下拉刷新 */ _refresherrefresh: function(e){ // recycleView.splice(0,recycleView.getList().length) // recycleView.splice(this.data.chatList.length-1,recycleView.getList().length) recycleView.splice(0,recycleView.getList().length,this.data.chatList) this.setData({ isstoppull:false }) }, /** * 生命周期函数--监听页面显示 */ onShow: function () { }, /** * 生命周期函数--监听页面隐藏 */ onHide: function () { }, /** * 生命周期函数--监听页面卸载 */ onUnload: function () { }, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh: function () { }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { }, onTapSelecPhoto: function () { let that = this wx.chooseImage({ count: 1, sizeType: ['original', 'compressed'], sourceType: ['album', 'camera'], success(res) { //tempFilePath可以作为img标签的src属性显示图片 const tempFilePaths = res.tempFilePaths console.log("YM------>图片内容", tempFilePaths) localMessageId += 1 that.sendPhoto(tempFilePaths[0],localMessageId) let tempFileObj = { localMessageId, localPath: tempFilePaths[0] } that.init.tempFileList.push(tempFileObj) that.uploadPicList() } }) }, upLoadPic: function (tempFile, resolve, reject) { // var content = util.JSON_to_URLEncoded(data) var updateApi = app.getApi(app.apiConstant.uploadOnly) let suffix = util.getFileSuffix(tempFile.localPath) let fileName = fileUtil.getDetailsFileName(suffix) // 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式 console.log("YM------>开始上传任务", tempFile) wx.uploadFile({ url: updateApi, filePath: tempFile.localPath, name: 'file', formData: { saveName: fileName }, success(res) { let result = JSON.parse(res.data) if (!result.success) { reject(`错误码:${result.code}`) return } let data = result.data let fileUrl = app.getApi(app.apiConstant.imgUrl).concat(data.saveName) tempFile.netPath = fileUrl tempFile.saveName = data.saveName resolve(tempFile) }, fail(error) { console.log("YM---->错误", error) reject(`${tempFile.localPath}出现错误`) }, }); }, /** * 批量同步异步上传图片 */ uploadPicList: function () { let that = this let promiseList = [] this.init.tempFileList.forEach(file => { let promise = new Promise(function (resolve, reject) { that.upLoadPic(file, resolve, reject) }); promiseList.push(promise) }) Promise.all(promiseList) .then((values) => { console.log("上传的结果",values); // let photoMessage = chatMessageUtil.createChatPhotoMessage(that.init.commonMessage, "图片路径") let result = values[0] that.updateMessageUI(result.localMessageId) }).catch((error) => { console.log(error) wx.showToast({ title: `图片上传错误码,请检查网络`, }) }); }, /** * 更新已经存在的消息 * @param {*} messageId 根据已经存在的消息ID去查询历史消息记录 */ updateMessageUI: function(messageId){ let allList = recycleView.getList() console.log("YM---->列表数据",allList) let findeResult = recycleView.getList().find(element => element.localMessageId == messageId) console.log("YM---->查找的结果",findeResult) if(!findeResult) { return } let index = recycleView.getList().indexOf(findeResult)//找到既定值的索引位置 console.log("YM---->索引位置",index) findeResult.sendStatus = sendStatus.SEND_SUCCESS //更改发送状态为成功 recycleView.update(index,findeResult,callBack => { console.log("YM---->列表的整体数据",recycleView.getList()) }) }, /** * 发送文字 */ onTapSendText: function () { let textMessage = chatMessageUtil.createChatTextMessage(this.init.commonMessage, this.init.messageText) this.sendMessage(textMessage) }, /** * 发送图片 */ sendPhoto: function(photoPath,localMessageId){ let photoMessage = chatMessageUtil.createChatPhotoMessage(this.init.commonMessage, photoPath,localMessageId) this.sendMessage(photoMessage) }, onBindInput: function (e) { this.init.messageText = e.detail.value }, sendMessage: function (message) { let tempMessage = JSON.parse(JSON.stringify(message))//需要重新copy一份对象,否则会修改之前的内容 recycleView.append(tempMessage, callback => { let allList = recycleView.getList() this.setData({ selectIndex: allList.length - 1 }) }) } })追加数据的问题:
let tempMessage = JSON.parse(JSON.stringify(message))//需要重新copy一份对象,否则会修改之前的内容 recycleView.append(tempMessage, callback => {//数据渲染后的回调监听 let allList = recycleView.getList()//获取页面全部数据 this.setData({ selectIndex: allList.length - 1 }) })修改每一个item的宽高
/** * 生命周期函数--监听页面初次渲染完成 */ onReady: function () { var recycleView = createRecycleContext({ id: 'recycleId', dataKey: 'chatList', page: this, // itemSize: { // 这个参数也可以直接传下面定义的this.itemSizeFunc函数 // height: rpx2px(220), // width: rpx2px(750) // } itemSize:this.itemSizeFunc }) // this.cacluteWidthAndHeight() recycleView.append(this.data.chatList) // this.cacluteChatContentHeight() }, /** * 动态计算每一个item的宽高 * @param {object} item 当前item显示的内容 * @param {Integer} idx 当前item索引值 */ itemSizeFunc: function (item, idx) { // console.log("YM--item",item) // console.log("YM--idx",idx) return { width: rpx2px(220), height: rpx2px(400) } },清空并追加新的数据
/** * * @param {*} e 下拉刷新 */ _refresherrefresh: function(e){ // 清空数据recycleView.splice(0,recycleView.getList().length) // 删除指定数据recycleView.splice(this.data.chatList.length-1,recycleView.getList().length) //删除制定数据并进行替换数据recycleView.splice(0,recycleView.getList().length,this.data.chatList) console.log("YM--->下拉刷新") this.setData({ isstoppull:false }) },下拉刷新 下拉刷新是根据网上改的recycle-view的源码做的,因为源码在npm中,所以如果使用npm进行重新构建的话,修改的部分会还原,所以要么换种方式,要么就把代码拿出来不使用npm构建
官方文档:https://developers.weixin.qq.com/miniprogram/dev/extended/component-plus/recycle-view.html
recycle-view添加下拉刷新:https://www.cnblogs.com/han-guang-xue/p/13048505.html