微信小程序-模仿绘制聊天界面

article/2025/8/25 19:52:07

参考文章

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}

后续待补充

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


http://chatgpt.dhexx.cn/article/VmIMW35W.shtml

相关文章

微信聊天小程序——(二、账号的注册与登录)

具体效果&#xff1a; 目录 二、账号的注册与登录 步骤一、获取用户信息 步骤二、用户输入账号密码&#xff08;在注册页面中&#xff09; 步骤三、将获取到的值放到我们的数据库中&#xff08;在注册页面中&#xff09; 步骤四、登录的页面逻辑 步骤五、登录页面的实现 …

微信聊天小程序——(三、获取好友列表)

三、获取好友列表 步骤一、展示所有好友的推荐列表&#xff08;friends页面&#xff09; 具体效果&#xff1a; 实现思路&#xff1a; 我们有我们的用户数据库表即&#xff1a;uers循环我们的数据库用户表&#xff0c;达到所有的用户信息&#xff0c;即&#xff1a;userLi…

微信聊天小程序——(五、添加好友)

五、添加好友 步骤一、通过搜索添加好友 具体效果&#xff1a; 思路&#xff1a; 本质上来讲&#xff0c;就是通过输入框得到好友账号信息&#xff0c;之后再数据库中查询&#xff0c;最后返回并渲染查询结果。首先&#xff0c;得到输入框的值&#xff0c;并传递到我们的页面…

支付宝小程序平台的IM聊天插件

文章目录 前言一、用户端1.基本展示2.难处理的点二、另一用户端1.前端websocket的整合2.手机息屏websocket断线问题2.websocket服务端配置3.后端整合websocket作为服务端&#xff0c;传输消息给前端 总结 前言 最近工作需求来了个项目&#xff0c;前景为在支付宝平台上发布一个…

使用 Python 编写一个聊天小程序

欢迎关注 “小白玩转Python”&#xff0c;发现更多 “有趣” 本篇文章分享如何用相当简洁的 Python 代码制作一个简单的聊天应用程序。更重要的是&#xff0c;我已经实现了没有任何第三方依赖的代码&#xff01; 首先&#xff0c;我创建了一个聊天服务器&#xff0c;通过它可以…

小米手机解BL锁教程

1.找到设置&#xff0c;找到我的设备 2.点击全部参数&#xff0c;多点几下miui版本&#xff0c;直到弹出开发者模式提醒。 3.返回&#xff0c;找到更多设置 4.找到开发者选项 5.找到设备定状态 6.绑定账号和设备&#xff0c;关机&#xff0c;按开键加音量减&#xff0c;进去fas…

安卓搞机玩机-什么是“锁 ” BL锁 屏幕锁 账号锁 设备锁等分析

相信把玩安卓机型的友友们都大概了解机型的锁是什么概念。但有些友友可能还分不清楚具体锁的概念。今天这个帖子由浅入深的带你了解安卓机型中各种“锁”的概念.这类话题比较敏感。只是大概带你分清楚锁的分类和基本对应的解锁方式. 一.屏幕锁【 图案锁 指纹锁 数字锁 人…

万能小米手机解锁,刷机,默认破解BL锁

写在第一条:下载之前请注意 本软件不支持最新机型,只支持4代和4代以前的机型,请注意 早就想给自己的小米手机刷机了&#xff0c;奈何一直没有门&#xff0c;最后还在求助了万能的淘宝&#xff0c;刚刚在淘宝花了 30大洋买的刷机工具&#xff0c;刷机成功之后&#xff0c;才反…

xiaomi 小米 红米redmi 秒解锁BL锁,不用等,在线秒解锁BL工具介绍

xiaomi 小米 红米redmi 秒解锁BL锁,不用等&#xff0c;在线秒解锁BL Xlaomi Redmi K40 Gaming Xlaomt Poco F3 GT Gaming Xaoml Poco X3 GT Xaoml Redmi Note9 5G Xlaomi Redml Note 10 Pro 5G Xlaoml Redmi Note 10 5G Xlaoml Redml Note 10T 5G Xlaoml Poco M3 Pro 5G Xaoml…

小米手机解BL锁、线刷详细教程,适用于小米全系列手机

[教程] 小米手机解BL锁、线刷详细教程&#xff0c;适用于小米全系列手机 这几天看到论坛里很多人在问怎么线刷&#xff0c;下面我就做个详细的线教程大家看一下高手别喷我哈 此教程只适合刷官方MIUI包 进入正题。 第一步&#xff1a;解BL锁 1.浏览器打开申请解锁小米手机点击立…

android@解bl锁@twrp的刷入和使用问题

文章目录 解bl锁步骤 附:MIUI线刷工具待线刷设备扫面和检查使用方式线刷工具卡住的关闭方法 第三方recoverytwrp解密datadata decrypt清除data后用twrp中刷入文件刷入面具 如何找到合适的TWRP rec ref 解bl锁 不是所有android设备都支持解锁下面是以最为典型的MIUI android设备…

02_Lock锁

首先看一下JUC的重磅武器——锁&#xff08;Lock&#xff09; 相比同步锁&#xff0c;JUC包中的Lock锁的功能更加强大&#xff0c;它提供了各种各样的锁&#xff08;公平锁&#xff0c;非公平锁&#xff0c;共享锁&#xff0c;独占锁……&#xff09;&#xff0c;所以使用起来…

红米note10 pro机型解除“账号锁”的一些操作案例 mtk机型强解bl锁

前言。操作解除锁类案例只限于自己的机型&#xff0c; 因手机号长期不用或者忘记密码导致账号锁出现的问题 机型哦 我经常在csdn网站分享一些玩机中的实测资源和玩机常识。遇到很多玩机的友友&#xff0c;其中我分享的一个资源&#xff0c;这个粉丝朋友下载后在网吧操作不了。…

穷举法解华为bl锁

穷举法解华为bl锁 python3代码测试截图 灵感来自于&#xff1a;https://blog.csdn.net/qq_40169767/article/details/90481748 但是我不懂shell脚本&#xff0c;那个脚本又运行不了&#xff0c;所以我用python写了一个&#xff0c; 穷举要0.05s*9999999999999999/60*60*24*3651…

玩机搞机---关于安卓机型工厂固件 刷机 端口解密 解bl锁 写串 nv损坏 等相关常识

*******工程机和工厂固件方面的常识 可能很多玩机友友对什么是工厂固件比较陌生。那么今天的话题就围绕这个和大家讨论下。其实一般厂家的流程都是在一部机型推放市场之前&#xff0c;需要经过预研企划、研发设计、全面测试等诸多环节。在这一整个改善的全过程中&#xff0c;厂…

小米解锁BL锁(普通解锁)

关于BL锁&#xff0c;BootLoader锁&#xff0c;WIKI上的解释简单一点说就是 启动操作系统的软件&#xff0c;也就是引导程序&#xff0c;用于初始化内存等。 官方的说法是防止篡改程序所造成用户的经济和数据损失&#xff0c;网上的说法是可定义程度高&#xff0c;可卸载系统预…

Android系统刷机教程之解bl锁

由于安卓系统是基于linux开发的&#xff0c;属于嵌入式操作系统。在嵌入式操作系统中&#xff0c;BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图&#xff0c;从而将系统的软硬件环境带到一个合适状态&#xff0c;以便为最终调用操作系统内核…

oppo r11 r11t解BL锁安装面具magisk详细教程

OPPO r11 r11t是高通骁龙660处理器&#xff0c;这个系列要解bl锁网上有很多教程比较http://rom点7to点cn/jiaochengdetail/16880 解好BL锁后就可以找三方rec&#xff0c;刷入rec 进入三方rec&#xff0c;把小包发送到手机硬盘&#xff0c;安装小包&#xff0c;再次重启进入三方…

华为手机一键解锁工具箱下载 | 华为手机解BL锁软件: 支持解锁bootloader,刷写recovery功能

文章目录 1. 软件介绍2. 特色功能3. 资源站点4. 下载地址5. 软件截图6. 安装教程7. 使用教程7.1. 解锁BL 1. 软件介绍 通过这款华为手机实用工具箱可以对你的华为手机系列进行刷机、解锁等操作&#xff0c;网上这种华为刷机解锁工具比较少&#xff0c;那么这款华为手机实用工具…

小米手机解bl锁正规方法!

申请解锁小米手机http://www.miui.com/unlock/download.html在手机上打开开发者模式&#xff0c;在开发者模式下-》设备解锁状态 然后关了wifi用手机卡上网绑定设备。 之后在菜单上选择清除所有数据&#xff0c;然后进入系统即可。