参考文章

1、小程序模仿微信聊天界面
2、微信小程序实现仿微信聊天界面(各种细节处理)
3、微信小程序之页面中关于聊天框三角形的制作和使用
4、仿微信聊天记录时间显示

5、微信小程序-同时获取麦克风、相机权限、获取多个权限
6、【uni-app】模仿微信实现简易发送/取发语音功能

7、微信小程序实现wxml中数据保留小数或取整

前言

代码参考自上述文章,样式和功能上根据自己需要做了一些改动以及删减,灰常感谢上述博主大大。ps:软键盘弹出还未进行测试。
消息交互的实现使用openfire,这里代码不做展示。

-----------------------2022/07/21修改-添加时间显示
-----------------------2022/07/22修改-发送按钮、空白消息提示
-----------------------2022/07/26修改-图片、语音消息

效果图

整体效果:

发送语音时(丑了点哈哈哈哈哈):


点击加号图标时:

代码

1、wxml

<wxs module="filters" src="../../../../utils/addmul.wxs"></wxs>
<view><view ><scroll-view scroll-y scroll-into-view='{{toView}}'  style='height: {{scrollHeight}};' refresher-enabled="true" bindrefresherrefresh="loadMore" refresher-triggered="{{triggered}}"><view class='scrollMsg' ><block wx:key="key" wx:for='{{msgList}}' wx:for-index="index"><!-- 时间显示,时间间隔为5分钟(5分钟内的消息不必再显示时间) --><view class="showTime" wx:if="{{item.showTime !== null}}">{{item.showTime}}</view><!-- 单个消息1 客服发出(左) --><view class="server" wx:if="{{item.jid == 'server'}}" id='msg-{{index}}'><view class="serverIcon"><image src='{{head_img}}'></image></view><view class="serverContent"><view class="Angle"></view><view class="Data"><view class="leftMsg" wx:if="{{item.type == '1' }}">{{item.msg}}</view><view class="leftMsg" wx:if="{{item.type == '2' }}"><image src="{{item.msg}}" class="image" catchtap="picture" data-src="{{item.msg}}"></image></view><view class="leftMsg" wx:if="{{item.type == '3' }}"><view bindtap='playVoice' data-item="{{item}}" data-index="{{index}}"><image style='height:32rpx;width:32rpx;'src="{{imgs.yyxx}}" mode="aspectFit"></image>{{filters.toFix(item.duration / 1000)}}"</view></view></view></view></view><!-- 单个消息2 用户发出(右) --><view class="customer" wx:else id='msg-{{index}}'><!-- 发起方的聊天框 --><view class="customerContent"><view class="Data"><view class="rightMsg" wx:if="{{item.type == '1' }}">{{item.msg}}</view><view class="rightMsg" wx:if="{{item.type == '2' }}"><image class="image" src="{{item.msg}}" catchtap="picture" data-src="{{item.msg}}"></image></view><view class="rightMsg" wx:if="{{item.type == '3' }}"><view bindtap='playVoice' data-item="{{item}}" data-index="{{index}}">{{filters.toFix(item.duration / 1000)}}"<image style='height:32rpx;width:32rpx;margin-right:28rpx;'src="{{imgs.yyxx}}" mode="aspectFit"></image></view></view></view><view class="AngleRight"></view></view><!-- 发起方的头像 --><view class="serverIcon"><image  src='{{head_img}}'></image></view></view></block></view></scroll-view></view><!-- 底部键盘、语音、加号 --><view class='inputRoom' style="bottom: {{inputBottom  + 'px'}}"><image src='{{!voice ? imgs.icon_yy : imgs.xjp}}' catchtap="addSpeakMsg"  mode='widthFix'></image><input wx:if="{{!voice}}" bindconfirm='sendClick' adjust-position='{{false}}' value="{{inputVal}}" confirm-type='send' bindfocus='focus' bindblur='blur' bindinput="getInputVal" maxlength="100"></input><view wx:else class="touch" bindtouchstart="touchdown"  bindtouchend="touchup" bindtouchmove="touchmove">长按 说话</view><image src='{{imgs.icon_gdgn}}' mode='widthFix' catchtap="addOtherFormatMsg"></image></view><!-- 点击加号图标 --><view class="chat-camera" wx:if="{{camera}}"><view wx:for="{{feature}}" wx:key="index" class="camera-feature" catchtap="featch" data-index="{{index}}"><view class="feature-src"><image src="{{item.src}}"></image></view><view class="feature-text">{{item.name}}</view></view></view></view><!-- 语音遮罩层 -->
<view class="voice-mask" wx:if="{{mask}}"><!--语音条 --><view class="voice-bar {{needCancel ? 'voiceDel' : ''}}"><image src="{{imgs.sb_c}}" class="voice-volume {{needCancel ? 'voiceDel' : ''}}"></image></view><!-- 底部区域 --><view class="voice-send"><!-- 取消图标 --><view class="voice-middle-wrapper"><!-- 取消 --><view class="voice-left-wrapper"><view class="voice-middle-inner close {{needCancel ? 'bigger' : ''}}"><image src="{{imgs.voiceCancel}}" class="close-icon"></image></view></view><view class="send-tip {{needCancel ? sendTipNone:''}}">{{sendtip}}</view></view> <!-- 底部语音显示 --><view class="mask-bottom"><image src="{{imgs.ht}}"></image></view></view>
</view>

2、wxss

page {background-color: #f1f1f1;}.inputRoom {width: 100vw;height: 60px;border-top: 1px solid #EEE;background-color: #fff;position: fixed;bottom: 0;display: flex;flex-direction: row;justify-content: space-between;align-items: center;z-index: 20;padding: 0 2vw;}.inputRoom image{width: 7vw;}input {width: 70vw;height: 45%;background-color: #F2F2F2;border-radius: 2px;padding: 4px;font-size: 28rpx;color: #444;}.touch{width: 72vw;height: 60%;text-align: center;background-color: #F2F2F2;border-radius: 2px;padding: 4px;font-size: 28rpx;color: #444;}.leftMsg {padding: 2vh 2.5vw;background-color: #fff;border-radius: 10rpx;z-index: 10;font-size: 14px;color: #3B3B3B;line-height: 20px;font-weight: 400;}.rightMsg {font-size: 14px;line-height: 20px;padding: 2vh 2.5vw;background-color: #149C89;border-radius: 10rpx;z-index: 10;color: #FDFDFD;font-weight: 400;}.Angle {display:flex;width:0;height:0;border-width:10px;border-style:solid;border-color:transparent #fff transparent transparent;}.AngleRight {display:flex;width:0;height:0;border-width:10px;border-style:solid;border-color:transparent transparent transparent #149C89 ;}.showTime{display: flex;justify-content: center;color:#AEAEAE;font-size: 14px;padding: 1vh 0;}.server{display: flex; padding: 2vh 11vw 2vh 2vw;flex-direction: row; justify-content: flex-start; align-items: center;}.serverIcon{width: 10vw; height: 10vw;}.serverIcon image{width: 100%;height: 100%;}.serverContent{width: 71vw; height: auto;  display: flex; justify-content: flex-start; align-items: center; z-index: 9;}.customer{display: flex; justify-content: flex-end; padding: 1vh 2vw 1vh 11vw;align-items: center;}.customerContent{width: 71vw; height: auto;  display: flex; justify-content: flex-end;align-items: center; z-index: 9;}.chat-camera{width: 100%;height: 100px;float: left;overflow: hidden;background-color: #EDEDED;overflow-y: auto;margin-top: 60px;}.camera-feature{margin: 5% 0 0 5%;width: 18.75%;float: left;overflow: hidden;text-align: center;font-size: 20rpx;}.feature-src{background-color: #fff;border-radius: 15rpx;float: left;width: 80rpx;height: 80rpx;margin: 0 calc(50% - 40rpx);text-align: center;}.feature-src>image{width: 40rpx;height: 40rpx;margin:20rpx;}.feature-text{width: 100%;float: left;margin-top: 10rpx;}.image{max-width: 71vw;max-height: 71vh;}/* 语音录制弹窗 */
.voice-mask{position:fixed;top:0;right:0;bottom:60px;left:0;/* display: flex;flex-direction: column;justify-content: flex-end;align-items: center; */background-color: rgba(0,0,0,0.8);
}
.voice-bar{position: absolute;left:50%;top: 50%;width: 45%;transform: translate(-50%,-30%);/* width: 230rpx; */height:150rpx;background-color:#51ff50;border-radius: 26rpx;margin-bottom: 220rpx;
}
.voiceDel{left:80rpx;top: 52%;width: 170rpx !important;transform: translateX(0%);transform: translateY(-30%);background-color: red;
}
.voice-volume{position: relative;top: 50%;left: 50%;transform: translate(-50%,-50%);width: 50%;height: 77%;
}
.volumeDel{width: 80rpx;
}
.trangle-bottom{position: absolute;bottom: -38rpx;left:50%;transform: translateX(-50%);border-width: 20rpx;border-style: solid;border-color: #51FF50 transparent transparent transparent;
}
.trangleDel{border-color: red transparent transparent transparent;
}
.voice-send{position: absolute;bottom: 0;width: 100%;
}
.voice-middle-wrapper{width: 100%;display: flex;position:relative;justify-content: space-between;align-items: flex-end;margin-bottom: 40rpx;
}
.voice-left-wrapper{display: flex;flex-direction: column;justify-content: center;align-items: flex-end;
}
.cancel-del{display:none;
}
.delTip{display:block;color:#bfbfbf;margin: 0 22rpx 18rpx 0;
}
.voice-middle-inner{display: flex;justify-content: center;align-items: center;background-color: rgba(0,0,0,0.2);width: 140rpx;height: 140rpx;border-radius: 50%;
}
.close{transform: rotate(350deg);margin-left: 80rpx;
}
.bigger{width: 170rpx;height: 170rpx;
}
.to-text{transform: rotate(10deg);margin-right: 80rpx;
}
.close-icon{width: 80rpx;height: 80rpx;
}
.wen{font-size: 40rpx;color:#bfbfbf;
}
.send-tip{position: absolute;left: 50%;bottom:0rpx;transform: translate(-50%,36%);color:#bfbfbf;
}
.sendTipNone{display: none;
}
.mask-bottom{position: relative;width: 100%;height:190rpx;border-top: #BABABB 8rpx solid;border-radius: 300rpx 300rpx 0 0;background-image: linear-gradient(#949794,#e1e3e1);
}
.mask-bottom image{position: absolute;width: 60rpx;height: 60rpx;top: 0;right:0;bottom: 0;left: 0;margin: auto;
}

3、.json

{"usingComponents": {}
}

4、.js

const app = getApp();
Page({/*** 页面的初始数据*/data: {//图标路径imgs:{icon_yy:"/icon_yy.png",icon_gdgn:"/icon_gdgn.png",ht: "ht.png",sb: "sb.png",xjp:"xjp.png",yyxx:"yyxx.png",voiceCancel:"voiceCancel.png",sb_c:"sb_c.png"},//对方头像,可从上个页面获取过来head_img:"/icon_gdgn.png",//输入inputVal : '',//下拉加载状态triggered: true,//记录前一条信息的时间戳-用于时间转换prevFirst: '',//记录当前信息列表的第一条信息的时间戳,用于下次查询curTopTimeStamp:'',//一次查询几条信息pagenum:10,//触发上拉操作+1index:0,msgList : [{msgid:'001',//发送方idjid: 'server',//接收方tojid: 'customer',timestamp:'1658136237',msg: '你喜欢看明星大侦探吗?',type: '1',isread:'1',},{msgid:'002',//发送方jid: 'customer',//接收方tojid: 'server',timestamp:'1658136357',msg: '喜欢的,你呢?',type: '1',isread:'1',},{msgid:'003',//发送方jid: 'server',//接收方tojid: 'customer',timestamp:'1658136657',msg: '我也喜欢的,你喜欢里面的谁呢',type: '1',isread:'1',},{msgid:'004',//发送方jid: 'server',//接收方tojid: 'customer',timestamp:'1658309457',msg: '你怎么不说话了?',type: '1',isread:'1',},{msgid:'005',//发送方jid: 'customer',//接收方tojid: 'server',timestamp:'1658481572',msg: '不想说不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话不想说话话',type: '1',isread:'1',},],//---高度信息----scrollHeight: 'calc(100vh - 60px)',inputBottom: 0,keyHeight : 0,windowHeight : 0,windowWidth : 0,//功能框高度featureHeight:0,toView : '',//点击加号camera: false,//点击语音voice:false,//是否正在说话isSpeaking : false,recorderManager: null, //managerinnerAudioContext: null, //音频播放managersendtip: '松 开 发 送', // 录音过程中提示//播放语音中isPlaying:false,palyingMsgData: null, //记录正在播放的音频对象//遮罩层mask:false,//定义录音是否发送isClock:true,//需要取消(但是还没有取消)needCancel:false,//记录“取消发送”图标坐标位置,用于判断是否想要取消发送top:'',left:'',right:'',bottom:'',// 功能 -图标集合feature:[{ src: 'camera.png', name: '相册' }],},/*** 生命周期函数--监听页面加载*/onLoad(options) {const that = this;that.setData({windowWidth:wx.getSystemInfoSync().windowWidth,windowHeight:wx.getSystemInfoSync().windowHeight,//对方账号tojid: 'server',//该页导航栏标题从上一页传递过来name:decodeURIComponent(options.name),//对方头像从上一页传递过来head_img:decodeURIComponent(options.head_img),},(res)=>{//页面切换,更换页面标题wx.setNavigationBarTitle({title: that.data.name });//后续考虑每次退出页面时,将信息存入缓存?记录最早一条的时间戳,下次查询从这个时间戳开始查询//此处可调用接口获取已有的信息。that.getMsgList();//初始化音频相关that.initVoiceConfig();})},//调用接口查询信息列表getMsgList(){//根据自己需要写取信息的逻辑,此处先使用默认消息var msgList = that.data.msgList;dealMsg(msgList)},//处理信息并保存渲染dealMsg(msgList){const that = this;//需要对信息集合进行处理-时间的显示与否for (var i = 0; i < msgList.length; i++){let list = msgList[i];let showTime = this.msgTimeFormat(list.timestamp,i);list['showTime'] = showTime;}that.setData({msgList:msgList,toView:'msg-' + (that.data.msgList.length - 1),})},//上拉触发事件loadMore(){//根据实际业务写上拉触发的时间},/*** 生命周期函数--监听页面初次渲染完成*/onReady: function (options) {},/*** 生命周期函数--监听页面显示*/onShow() {app.pageBindOpEvent(this.onConnect, this.onMessage);},//获取聚焦focus(e){const that = this;let keyHeight = e.detail.height;that.setData({camera : false,scrollHeight: (that.data.windowHeight - keyHeight - 60) + 'px',keyHeight: keyHeight});that.setData({toView: 'msg-' + (that.data.msgList.length - 1),inputBottom: keyHeight})},//失去聚焦(软键盘消失)blur(e) {const that = this;that.setData({scrollHeight: 'calc(100vh - 60px)',inputBottom: 0})that.setData({toView: 'msg-' + (that.data.msgList.length - 1)})},//获取输入内容getInputVal: function(e) {this.setData({inputVal: e.detail.value})},//发送点击监听sendClick: function(e) {const that = this;let value = that.data.inputVal;let msgList = that.data.msgList;if(value && !value.replace(/\s+/g, '').length == 0){//限制输入,为空或空格时不发送// 塞时间let timestamp = Date.parse(new Date());let showTime = this.msgTimeFormat(timestamp,that.data.msgList.length)msgList.push({msgid:'010',//发送方jid: 'customer',//接收方tojid: 'server',timestamp: timestamp,msg: value,type: '1',isread:'1',showTime:showTime})}else{//提示wx.showToast({title: '发送消息为空!',icon:'none'})}that.setData({msgList : msgList,inputVal : '',toView:'msg-' + (that.data.msgList.length - 1),});},/*** 聊天时间 格式化* 规则:*  1. 每五分钟为一个跨度*  2. 今天显示,小时:分钟,例如:11:12*  3. 昨天显示,昨天 小时:分钟 例如:昨天 11:12*  4. 日期差大于一天显示,年月日 小时:分钟 例如:2021年9月30日 11:12* @param timestamp,index * @returns {string|null}*/msgTimeFormat(timestamp, index) {const that = this;//时间戳转变为时间let date = timestamp.toString().length == 13 ? new Date(parseInt(timestamp)) : new Date(parseInt(timestamp * 1000));let time = '';//第一条消息if (0 == index){that.setData({prevFirst : timestamp})let prev = new Date(date);let next = new Date();let day = next.getDate() - prev.getDate();day = day >= 0 ? day : -(day);if (day > 1) {//时间间隔大于一天,显示YYYY年MM月DD日 HH:mmtime = this.dateFormatChina(new Date(that.data.prevFirst.toString().length == 13 ? new Date(parseInt(that.data.prevFirst)) : new Date(parseInt(that.data.prevFirst * 1000))));} else if (day === 1) {time = '昨天 ' + prev.getHours() + ":" + this.timeAppendZero(prev);} else {time = prev.getHours() + ":" + this.timeAppendZero(prev);}return time;}let prev = new Date(that.data.prevFirst.toString().length == 13 ? new Date(parseInt(that.data.prevFirst)) : new Date(parseInt(that.data.prevFirst * 1000)));let next = new Date(date);let day = Math.floor( (next-prev) / (24*60*60*1000) );let minutes = Math.floor((next-prev) / (1000 * 60));let dayT = new Date().getDate() - next.getDate();let yesterdayFlag = dayT === 1 || dayT === -1;let todayFlag = dayT === 0;/*下标越界标志未越界且分钟差大于5,将当前消息日期作为比较值并替换prevFirst,并根据规则格式化越界则表示下标走到了最后一位,将其作为要显示的日期赋值给prev,并根据规则格式化*/let indexOutFlag = that.data.msgList.length !== (index + 1);if (indexOutFlag && minutes > 5) {that.setData({prevFirst : timestamp})if (!todayFlag && !yesterdayFlag) {return this.dateFormatChina(next);} else {prev = new Date(date);if (yesterdayFlag) {return '昨天 ' + prev.getHours() + ":" + this.timeAppendZero(prev);}}} else {prev = new Date(date);}if (yesterdayFlag && minutes >= 5) {return '昨天 ' + prev.getHours() + ":" + this.timeAppendZero(prev);} else if (todayFlag && minutes >= 5) {return prev.getHours() + ":" + this.timeAppendZero(prev);}return null;},dateFormatChina(date) {return date.getFullYear() + "年" + (date.getMonth()+1) + "月" + date.getDate() + "日 " + date.getHours() + ":" + this.timeAppendZero(date);},timeAppendZero(time) {return time.getMinutes().toString().length === 1 ? '0' + time.getMinutes() : time.getMinutes();},//点击加号addOtherFormatMsg() {const that = this;that.setData({camera: !that.data.camera,voice:false,isSpeaking:false})that.setData({inputBottom: that.data.camera == true ? that.data.inputBottom + 100 : 0,scrollHeight: that.data.camera == true ? 'calc(100vh - 160px)' : 'calc(100vh - 60px)',})that.setData({toView: 'msg-' + (that.data.msgList.length - 1),})},//功能页-featch(e){const that = thislet index = e.currentTarget.dataset.indexif(index == 0){//相册-选择图片that.upload();}},//上传图片upload:function(e){const that = thislet msgList = that.data.msgList;// 微信选择图片wx.chooseImage({count: 3, // 最多一次性选择图片的数量 默认9// sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有// sourceType: ['album'], //, 'camera' 可以指定来源是相册还是相机,默认二者都有success: function (res) {//时间戳转换为时间// 判断时间戳是否为13位数,如果不是则*1000,时间戳只有13位数(带毫秒)和10(不带毫秒)位数的let timeTamp = Date.parse((new Date()));let showTime = that.msgTimeFormat(timeTamp,msgList.length);let tempFilePathLists = res.tempFilePaths;for (var i = 0; i < tempFilePathLists.length; i++){msgList.push({msgid:"00" + Math.random(),//发送方jid: "customer",//接收方tojid: 'server',timestamp: timeTamp,msg: tempFilePathLists[i],type: '2',isread:'0',showTime:showTime})//在此,图片的路径先使用微信临时文件,后续需要上传至服务器that.setData({msgList : msgList,toView:'msg-' + (that.data.msgList.length - 1),camera:false,scrollHeight:"calc(100vh - 60px)",inputBottom:0});},failed: function (res) {wx.showToast({title: '图片选择失败,请重试',icon: 'none'})},complete: function (res) {}});},/*** 点击看大图*/picture:function(e){let src = e.currentTarget.dataset.src;wx.previewImage({current: src,urls: [src]})},/*** 初始化语音录制和播放的配置数据*/initVoiceConfig() {const recorderManager = wx.getRecorderManager(); // 录音managervar msgList = this.data.msgList;recorderManager.onStart(() => {console.log('start')})recorderManager.onPause(() => {console.log('pause')})recorderManager.onStop((res) => {console.log('stop')// 录音时间小于一秒钟,提示录音时间过短if (res.duration < 1000) {wx.showToast({title: '说话时间太短',icon: 'error'});return;}// 防止出现录音结束了,录音弹框没有消失的问题clearInterval(this.timer);this.setData({isSpeaking: false,sendtip: '松 开 发 送'});var that = this;//封装消息if (that.data.isClock) {//时间戳转换为时间// 判断时间戳是否为13位数,如果不是则*1000,时间戳只有13位数(带毫秒)和10(不带毫秒)位数的let timeTamp = Date.parse((new Date()));let showTime = that.msgTimeFormat(timeTamp,msgList.length);msgList.push({msgid:"00" + Math.random(),//发送方jid: "customer",//接收方tojid: 'server',timestamp: timeTamp,msg: res.tempFilePath,duration: res.duration,type: '3',isread:'0',showTime:showTime});that.setData({msgList : msgList,toView:'msg-' + (that.data.msgList.length - 1)})}//在此,语音的路径先使用微信临时文件,后续需要上传至服务器});recorderManager.onFrameRecorded((res) => {const {frameBuffer} = resconsole.log('frameBuffer.byteLength', frameBuffer.byteLength)});this.data.recorderManager = recorderManager;//音频播放managerconst innerAudioContext = wx.createInnerAudioContext();innerAudioContext.onPlay(() => {console.log('开始播放');});innerAudioContext.onEnded(() => {console.log('音频自然播放结束');this.setData({palyingMsgData: null});});innerAudioContext.onStop((res) => {console.log("音频播放停止");});innerAudioContext.onError((res) => {console.log("音频播放失败" + res.errCode + "---errMsg=" + res.errMsg);this.setData({palyingMsgData: null});wx.showToast({title: '音频播放失败',icon: 'error'});});this.data.innerAudioContext = innerAudioContext;},//点击语音图标addSpeakMsg(){const that = this;//检查麦克风权限that.checkAuthorize().then((result) => {that.setData({camera:false,voice:!that.data.voice,scrollHeight:'calc(100vh - 60px)',inputBottom:0})that.setData({toView: 'msg-' + (that.data.msgList.length - 1),})}).catch((error) => {})},//按下说话touchdown: function (e) {console.log("手指按下")const query = wx.createSelectorQuery();var that= this;that.setData({isSpeaking: true,mask:true},(res)=>{//记录“取消发送”元素位置if (that.data.mask) {query.select('.close-icon').boundingClientRect()query.exec(function (res) {that.setData({top:res[0].top,left:res[0].left,right:res[0].right,bottom:res[0].bottom,})})}that.startVoice();speaking.call(that);});},/*** 录音:手指滑动,录音不发送*/touchmove: function(e){const that = this;let needCancel = false;let sendtip = '松 开 发 送';//判断当前触摸位置是否处于“取消发送”元素内if(e.touches[0].pageX >= that.data.left && e.touches[0].pageX <= that.data.right && e.touches[0].pageY >= that.data.top && e.touches[0].pageY <= that.data.bottom){needCancel = true;sendtip = '松 开 取 消';} that.setData({needCancel:needCancel,sendtip: sendtip})},/*** 录音:手指抬起,录音结束*/touchup: function (e) {console.log("手指抬起");const that = this;this.setData({isSpeaking: false,isClock:!that.data.needCancel,mask:false,},(res)=>{this.handleStopVoice(this);})},//开始录音startVoice: function () {console.log("startVoice----");// 如果此时正在播放语音,则停止this.handleStopPlayVoice(this);const options = {duration: 61000, //默认最长播放时长60秒 // sampleRate: 44100,// numberOfChannels: 1,// encodeBitRate: 192000,};if (this.data.isSpeaking) {this.data.recorderManager.start(options);}else{wx.showToast({title: '说话时间太短',icon: 'error'});return;}},/*** 结束录音以及处理相关逻辑*/handleStopVoice: function (that) {that.stopVoice();clearInterval(that.timer);that.setData({needCancel:false});},/*** 结束录音*/stopVoice: function () {console.log("stopVoice----");this.data.recorderManager.stop();},/*** 播放音频*/playVoice: function (e) {var that = this;var mData = e.currentTarget.dataset.item;var index = e.currentTarget.dataset.index;// 如果点击的是正在播放的语音,则停止语音播放if (that.data.palyingMsgData != null && that.data.palyingMsgData == mData.msg) {that.handleStopPlayVoice(that);return false;}//  如果点击的是未在播放的语音,播放之前先停掉别的语音播放that.stopPlayVoice();that.setData({palyingMsgData: mData.msg});//播放var voiceUrl = mData.msg;that.data.innerAudioContext.src = voiceUrl;that.data.innerAudioContext.play();},/*** 停止音频播放*/stopPlayVoice: function () {console.log('stopPlayVoice----');this.data.innerAudioContext.stop();},/*** 停止语音播放以及处理相关逻辑*/handleStopPlayVoice: function (that) {if (that.data.palyingMsgData != null) {// 停止语音播放that.stopPlayVoice();}},//检查授权-麦克风权限checkDeviceAuthorize: function () {return new Promise((resolve, reject) => {wx.getSetting({success:(res)=>{let auth = res.authSetting['scope.record']if (auth === true) { // 用户已经同意授权resolve()}else if (auth === undefined) {// 首次发起授权wx.authorize({scope: 'scope.record',success() {resolve()},fail(res) {}})}else if (auth === false) { // 非首次发起授权,用户拒绝过 => 弹出提示对话框wx.showModal({title: '授权提示',content: '请前往设置页打开麦克风',success: (tipRes) => {if (tipRes.confirm) {wx.openSetting({success: (settingRes) => {if (settingRes.authSetting['scope.record']) {resolve()}},})}}})}},})})},// 页面从前台变为后台时执行onHide: function () {app.pageunBindOpEvent();},/*** 生命周期函数--监听页面卸载*/onUnload() {const that = this;app.pageunBindOpEvent();that.data.innerAudioContext.destroy();//销毁这个实例},})/*** 麦克风帧动画*/
function speaking() {var that = this;var delayTime = 1000;var MAX_DURATION = 60000;var COUNTDOWN_DURATION = 50000;//话筒帧动画var duration = 0;that.timer = setInterval(function () {duration = duration + delayTime;console.log("duration==" + duration);//倒计时提示-10秒if (duration > COUNTDOWN_DURATION) {var djs = parseInt((MAX_DURATION - duration) / 1000);that.setData({sendtip: '录音倒计时:' + djs + 's'});}if (duration >= MAX_DURATION) {that.handleStopVoice(that);}}, delayTime);
}

5、addmul.wxs

var filters = {toFix2: function (value) {return parseFloat(value).toFixed(2)//此处2为保留两位小数},toFix1: function (value) {return parseFloat(value).toFixed(1)//此处1为保留一位小数},toFix: function (value) {return parseFloat(value).toFixed(0)//此处0为取整数}}module.exports = {toFix2: filters.toFix2,toFix1: filters.toFix1,toFix: filters.toFix}

后续待补充

语音暂停,从原来暂停的位置开始播放

微信小程序-模仿绘制聊天界面相关推荐

  1. 微信小程序 模仿华为音乐 列表界面

    微信小程序 模仿华为音乐 列表界面 index.js var app=getApp(); var listDatas = require("../../data/data_list.js&q ...

  2. 微信小程序-canvas绘制文字实现自动换行

    微信小程序-canvas绘制文字实现自动换行 在使用微信小程序canvas绘制文字时,时常会遇到这样的问题:因为canvasContext.fillText参数为 我们只能设置文本的最大宽度,这就产生 ...

  3. 微信小程序模仿开眼视频app(一)——视频首页、视频详情、分类

    可到我的github账号上去copy文件 先展示一下我实现了的功能吧 提示,如果有出现无法加载域名之类问题的的,可以在"设置"-"项目设置"-打钩"不 ...

  4. 在微信小程序中绘制图表(part2)

    本期大纲 1.确定纵坐标的范围并绘制 2.根据真实数据绘制折线 相关阅读: 在微信小程序中绘制图表(part1) 在微信小程序中绘制图表(part3) 关注我的 github 项目 查看完整代码. 确 ...

  5. 微信开发者工具 wxmi修改模版颜色_网站建设公司讲解:微信小程序的开发者工具界面...

    网站建设公司深圳市博纳网络信息技术有限公司()讲解:微信小程序的开发者工具界面 创建项目后,进入到微信开发者工具界面,界面大致可以分为6个区域:①菜单栏区域,②模拟器.编辑器.调试器显示与隐藏区域,③ ...

  6. 在微信小程序中绘制图表(part3)

    本期大纲 1.饼图绘制 2.如何添加动画效果 3.使用rollup构建项目 相关阅读: 在微信小程序中绘制图表(part1) 在微信小程序中绘制图表(part2) 关注我的 github 项目 查看完 ...

  7. 微信小程序实现websocket聊天室

    在微信小程序中建立聊天室. 目录 一.在web项目中导入javax.websocket-api-1.0-rc4.jar包 二.编写支持websocket的controller类 三.在微信小程序中编写 ...

  8. 微信小程序模仿开眼视频app(二)——搜索功能

    微信小程序模仿开眼视频app(一)有介绍首页.视频详情和分类部分 可到我的github账号上去copy文件 搜索部分 一开始没想要设置历史记录啊.热门搜索啊这些的,只是想把搜索框弄好看一点,无意中发现 ...

  9. 微信小程序如何刷新当前界面

    微信小程序如何刷新当前界面 在微信小程序开发的过程中,在一个页面中对数据操作之后我们大多数时间都需要刷新一下当前界面以把操作之后的结果显示出来,但是如何在执行操作后进行本页面的刷新就成了一个问题很大但 ...

最新文章

  1. 图形数据库Neo4j基本了解
  2. (C++)1023 组个最小数 简单贪心
  3. 阿里云能耗宝即将发布,助力中小企业绿色升级,参与碳中和万亿市场
  4. javascript map 排序_1Keys仅用1 kb的JavaScript制作钢琴
  5. django前后端结合_Vue+Django前后端项目构建
  6. Chialisp是什么
  7. scanf(%d,%d,a,b)在c语言所代表的含义,在C语言中,有语句“inta,b;scanf(a=%db=%d,a,b);”,下.._简答题试题答案...
  8. Xxmm_Move_Order_Print
  9. 对话王劲:无人驾驶每天能救500多条人命 | AI英雄
  10. python海龟交易策略_python 海龟交易法则 股票回测-双均线规则(一)
  11. 优秀的论文答辩PPT模板值得被应用
  12. 一步步学习SPD2010--第一章节--探索SPD2010(3)--理解SharePoint Designer 2010新功能
  13. 企业官网小程序搭建教程
  14. 20年的星际争霸,再回首
  15. RGB三分量与饱和度
  16. 中科蓝汛------低电量时间自定义功能实现
  17. php carbon详解,详解PHP使用日期时间处理器Carbon人性化显示时间
  18. 读取STM32F207/40x的CPU唯一ID(Unique Device ID)号方法
  19. 联想服务器系统初始化失败怎么回事,登陆进程初始化失败原因及解决办法
  20. Vue学习总结(全)

热门文章

  1. 解决vscode头文件<bits/stdc++.h>报错问题
  2. 基于JAVA酒店订房系统计算机毕业设计源码+系统+mysql数据库+lw文档+部署
  3. 白嫖项目基于ssm房屋租赁系统源码【开源项目】
  4. 基于matlab多绕组变压器模型的磁饱和式可控电抗器仿真建模方法,基于MATLAB多绕组变压器模型的磁饱和式可控电抗器仿真建模方法...
  5. 用VS2005制作网页对IIS进行操作
  6. Eclipse 创建 Hello World 工程
  7. IDEA建立jsp文件
  8. vue+antd搭建后台管理界面模版(PC端),适配中文、英文、日文 mock数据,开箱即用
  9. Axure RP Extension for Chrome:谷歌Chrome浏览器查看Axure RP原型 Chrome插件
  10. 超宽带(UWB)技术