參考文章
1、小程序模仿微信聊天界面
2、微信小程序?qū)崿F(xiàn)仿微信聊天界面(各種細(xì)節(jié)處理)
3、微信小程序之頁面中關(guān)于聊天框三角形的制作和使用
4、仿微信聊天記錄時(shí)間顯示
5、微信小程序-同時(shí)獲取麥克風(fēng)、相機(jī)權(quán)限、獲取多個(gè)權(quán)限
6、【uni-app】模仿微信實(shí)現(xiàn)簡易發(fā)送/取發(fā)語音功能
7、微信小程序?qū)崿F(xiàn)wxml中數(shù)據(jù)保留小數(shù)或取整
前言
代碼參考自上述文章,樣式和功能上根據(jù)自己需要做了一些改動(dòng)以及刪減,灰常感謝上述博主大大。ps:軟鍵盤彈出還未進(jìn)行測試。
消息交互的實(shí)現(xiàn)使用openfire,這里代碼不做展示。
-----------------------2022/07/21修改-添加時(shí)間顯示
-----------------------2022/07/22修改-發(fā)送按鈕、空白消息提示
-----------------------2022/07/26修改-圖片、語音消息
效果圖
整體效果:
發(fā)送語音時(shí)(丑了點(diǎn)哈哈哈哈哈):
點(diǎn)擊加號圖標(biāo)時(shí):
代碼
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">
<!-- 時(shí)間顯示,時(shí)間間隔為5分鐘(5分鐘內(nèi)的消息不必再顯示時(shí)間) -->
<view class="showTime" wx:if="{{item.showTime !== null}}">
{{item.showTime}}
</view>
<!-- 單個(gè)消息1 客服發(fā)出(左) -->
<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>
<!-- 單個(gè)消息2 用戶發(fā)出(右) -->
<view class="customer" wx:else id='msg-{{index}}'>
<!-- 發(fā)起方的聊天框 -->
<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>
<!-- 發(fā)起方的頭像 -->
<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>
<!-- 點(diǎn)擊加號圖標(biāo) -->
<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>
<!-- 底部區(qū)域 -->
<view class="voice-send">
<!-- 取消圖標(biāo) -->
<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({
/**
* 頁面的初始數(shù)據(jù)
*/
data: {
//圖標(biāo)路徑
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"
},
//對方頭像,可從上個(gè)頁面獲取過來
head_img:"/icon_gdgn.png",
//輸入
inputVal : '',
//下拉加載狀態(tài)
triggered: true,
//記錄前一條信息的時(shí)間戳-用于時(shí)間轉(zhuǎn)換
prevFirst: '',
//記錄當(dāng)前信息列表的第一條信息的時(shí)間戳,用于下次查詢
curTopTimeStamp:'',
//一次查詢幾條信息
pagenum:10,
//觸發(fā)上拉操作+1
index:0,
msgList : [{
msgid:'001',
//發(fā)送方id
jid: 'server',
//接收方
tojid: 'customer',
timestamp:'1658136237',
msg: '你喜歡看明星大偵探嗎?',
type: '1',
isread:'1',
},
{
msgid:'002',
//發(fā)送方
jid: 'customer',
//接收方
tojid: 'server',
timestamp:'1658136357',
msg: '喜歡的,你呢?',
type: '1',
isread:'1',
},{
msgid:'003',
//發(fā)送方
jid: 'server',
//接收方
tojid: 'customer',
timestamp:'1658136657',
msg: '我也喜歡的,你喜歡里面的誰呢',
type: '1',
isread:'1',
},
{
msgid:'004',
//發(fā)送方
jid: 'server',
//接收方
tojid: 'customer',
timestamp:'1658309457',
msg: '你怎么不說話了?',
type: '1',
isread:'1',
},
{
msgid:'005',
//發(fā)送方
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 : '',
//點(diǎn)擊加號
camera: false,
//點(diǎn)擊語音
voice:false,
//是否正在說話
isSpeaking : false,
recorderManager: null, //manager
innerAudioContext: null, //音頻播放manager
sendtip: '松 開 發(fā) 送', // 錄音過程中提示
//播放語音中
isPlaying:false,
palyingMsgData: null, //記錄正在播放的音頻對象
//遮罩層
mask:false,
//定義錄音是否發(fā)送
isClock:true,
//需要取消(但是還沒有取消)
needCancel:false,
//記錄“取消發(fā)送”圖標(biāo)坐標(biāo)位置,用于判斷是否想要取消發(fā)送
top:'',
left:'',
right:'',
bottom:'',
// 功能 -圖標(biāo)集合
feature:[
{ src: 'camera.png', name: '相冊' }
],
},
/**
* 生命周期函數(shù)--監(jiān)聽頁面加載
*/
onLoad(options) {
const that = this;
that.setData({
windowWidth:wx.getSystemInfoSync().windowWidth,
windowHeight:wx.getSystemInfoSync().windowHeight,
//對方賬號
tojid: 'server',
//該頁導(dǎo)航欄標(biāo)題從上一頁傳遞過來
name:decodeURIComponent(options.name),
//對方頭像從上一頁傳遞過來
head_img:decodeURIComponent(options.head_img),
},(res)=>{
//頁面切換,更換頁面標(biāo)題
wx.setNavigationBarTitle({
title: that.data.name
});
//后續(xù)考慮每次退出頁面時(shí),將信息存入緩存?記錄最早一條的時(shí)間戳,下次查詢從這個(gè)時(shí)間戳開始查詢
//此處可調(diào)用接口獲取已有的信息。
that.getMsgList();
//初始化音頻相關(guān)
that.initVoiceConfig();
})
},
//調(diào)用接口查詢信息列表
getMsgList(){
//根據(jù)自己需要寫取信息的邏輯,此處先使用默認(rèn)消息
var msgList = that.data.msgList;
dealMsg(msgList)
},
//處理信息并保存渲染
dealMsg(msgList){
const that = this;
//需要對信息集合進(jìn)行處理-時(shí)間的顯示與否
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),
})
},
//上拉觸發(fā)事件
loadMore(){
//根據(jù)實(shí)際業(yè)務(wù)寫上拉觸發(fā)的時(shí)間
},
/**
* 生命周期函數(shù)--監(jiān)聽頁面初次渲染完成
*/
onReady: function (options) {
},
/**
* 生命周期函數(shù)--監(jiān)聽頁面顯示
*/
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)
})
},
//獲取輸入內(nèi)容
getInputVal: function(e) {
this.setData({
inputVal: e.detail.value
})
},
//發(fā)送點(diǎn)擊監(jiān)聽
sendClick: function(e) {
const that = this;
let value = that.data.inputVal;
let msgList = that.data.msgList;
if(value && !value.replace(/\s+/g, '').length == 0){
//限制輸入,為空或空格時(shí)不發(fā)送
// 塞時(shí)間
let timestamp = Date.parse(new Date());
let showTime = this.msgTimeFormat(timestamp,that.data.msgList.length)
msgList.push(
{
msgid:'010',
//發(fā)送方
jid: 'customer',
//接收方
tojid: 'server',
timestamp: timestamp,
msg: value,
type: '1',
isread:'1',
showTime:showTime
}
)
}else{
//提示
wx.showToast({
title: '發(fā)送消息為空!',
icon:'none'
})
}
that.setData({
msgList : msgList,
inputVal : '',
toView:'msg-' + (that.data.msgList.length - 1),
});
},
/**
* 聊天時(shí)間 格式化
* 規(guī)則:
* 1. 每五分鐘為一個(gè)跨度
* 2. 今天顯示,小時(shí):分鐘,例如:11:12
* 3. 昨天顯示,昨天 小時(shí):分鐘 例如:昨天 11:12
* 4. 日期差大于一天顯示,年月日 小時(shí):分鐘 例如:2021年9月30日 11:12
* @param timestamp,index
* @returns {string|null}
*/
msgTimeFormat(timestamp, index) {
const that = this;
//時(shí)間戳轉(zhuǎn)變?yōu)闀r(shí)間
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) {
//時(shí)間間隔大于一天,顯示YYYY年MM月DD日 HH:mm
time = 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;
/*
下標(biāo)越界標(biāo)志
未越界且分鐘差大于5,將當(dāng)前消息日期作為比較值并替換prevFirst,并根據(jù)規(guī)則格式化
越界則表示下標(biāo)走到了最后一位,將其作為要顯示的日期賦值給prev,并根據(jù)規(guī)則格式化
*/
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();
},
//點(diǎn)擊加號
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 = this
let index = e.currentTarget.dataset.index
if(index == 0){
//相冊-選擇圖片
that.upload();
}
},
//上傳圖片
upload:function(e){
const that = this
let msgList = that.data.msgList;
// 微信選擇圖片
wx.chooseImage({
count: 3, // 最多一次性選擇圖片的數(shù)量 默認(rèn)9
// sizeType: ['compressed'], // 可以指定是原圖還是壓縮圖,默認(rèn)二者都有
// sourceType: ['album'], //, 'camera' 可以指定來源是相冊還是相機(jī),默認(rèn)二者都有
success: function (res) {
//時(shí)間戳轉(zhuǎn)換為時(shí)間
// 判斷時(shí)間戳是否為13位數(shù),如果不是則*1000,時(shí)間戳只有13位數(shù)(帶毫秒)和10(不帶毫秒)位數(shù)的
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(),
//發(fā)送方
jid: "customer",
//接收方
tojid: 'server',
timestamp: timeTamp,
msg: tempFilePathLists[i],
type: '2',
isread:'0',
showTime:showTime
}
)
//在此,圖片的路徑先使用微信臨時(shí)文件,后續(xù)需要上傳至服務(wù)器
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) {
}
});
},
/**
* 點(diǎn)擊看大圖
*/
picture:function(e){
let src = e.currentTarget.dataset.src;
wx.previewImage({
current: src,
urls: [src]
})
},
/**
* 初始化語音錄制和播放的配置數(shù)據(jù)
*/
initVoiceConfig() {
const recorderManager = wx.getRecorderManager(); // 錄音manager
var msgList = this.data.msgList;
recorderManager.onStart(() => {
console.log('start')
})
recorderManager.onPause(() => {
console.log('pause')
})
recorderManager.onStop((res) => {
console.log('stop')
// 錄音時(shí)間小于一秒鐘,提示錄音時(shí)間過短
if (res.duration < 1000) {
wx.showToast({
title: '說話時(shí)間太短',
icon: 'error'
});
return;
}
// 防止出現(xiàn)錄音結(jié)束了,錄音彈框沒有消失的問題
clearInterval(this.timer);
this.setData({
isSpeaking: false,
sendtip: '松 開 發(fā) 送'
});
var that = this;
//封裝消息
if (that.data.isClock) {
//時(shí)間戳轉(zhuǎn)換為時(shí)間
// 判斷時(shí)間戳是否為13位數(shù),如果不是則*1000,時(shí)間戳只有13位數(shù)(帶毫秒)和10(不帶毫秒)位數(shù)的
let timeTamp = Date.parse((new Date()));
let showTime = that.msgTimeFormat(timeTamp,msgList.length);
msgList.push(
{
msgid:"00" + Math.random(),
//發(fā)送方
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)
})
}
//在此,語音的路徑先使用微信臨時(shí)文件,后續(xù)需要上傳至服務(wù)器
});
recorderManager.onFrameRecorded((res) => {
const {
frameBuffer
} = res
console.log('frameBuffer.byteLength', frameBuffer.byteLength)
});
this.data.recorderManager = recorderManager;
//音頻播放manager
const innerAudioContext = wx.createInnerAudioContext();
innerAudioContext.onPlay(() => {
console.log('開始播放');
});
innerAudioContext.onEnded(() => {
console.log('音頻自然播放結(jié)束');
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;
},
//點(diǎn)擊語音圖標(biāo)
addSpeakMsg(){
const that = this;
//檢查麥克風(fēng)權(quán)限
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)=>{
//記錄“取消發(fā)送”元素位置
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);
});
},
/**
* 錄音:手指滑動(dòng),錄音不發(fā)送
*/
touchmove: function(e){
const that = this;
let needCancel = false;
let sendtip = '松 開 發(fā) 送';
//判斷當(dāng)前觸摸位置是否處于“取消發(fā)送”元素內(nèi)
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
})
},
/**
* 錄音:手指抬起,錄音結(jié)束
*/
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----");
// 如果此時(shí)正在播放語音,則停止
this.handleStopPlayVoice(this);
const options = {
duration: 61000, //默認(rèn)最長播放時(shí)長60秒
// sampleRate: 44100,
// numberOfChannels: 1,
// encodeBitRate: 192000,
};
if (this.data.isSpeaking) {
this.data.recorderManager.start(options);
}else{
wx.showToast({
title: '說話時(shí)間太短',
icon: 'error'
});
return;
}
},
/**
* 結(jié)束錄音以及處理相關(guān)邏輯
*/
handleStopVoice: function (that) {
that.stopVoice();
clearInterval(that.timer);
that.setData({
needCancel:false
});
},
/**
* 結(jié)束錄音
*/
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;
// 如果點(diǎn)擊的是正在播放的語音,則停止語音播放
if (that.data.palyingMsgData != null && that.data.palyingMsgData == mData.msg) {
that.handleStopPlayVoice(that);
return false;
}
// 如果點(diǎn)擊的是未在播放的語音,播放之前先停掉別的語音播放
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();
},
/**
* 停止語音播放以及處理相關(guān)邏輯
*/
handleStopPlayVoice: function (that) {
if (that.data.palyingMsgData != null) {
// 停止語音播放
that.stopPlayVoice();
}
},
//檢查授權(quán)-麥克風(fēng)權(quán)限
checkDeviceAuthorize: function () {
return new Promise((resolve, reject) => {
wx.getSetting({
success:(res)=>{
let auth = res.authSetting['scope.record']
if (auth === true) { // 用戶已經(jīng)同意授權(quán)
resolve()
}
else if (auth === undefined) {// 首次發(fā)起授權(quán)
wx.authorize({
scope: 'scope.record',
success() {
resolve()
},
fail(res) {
}
})
}
else if (auth === false) { // 非首次發(fā)起授權(quán),用戶拒絕過 => 彈出提示對話框
wx.showModal({
title: '授權(quán)提示',
content: '請前往設(shè)置頁打開麥克風(fēng)',
success: (tipRes) => {
if (tipRes.confirm) {
wx.openSetting({
success: (settingRes) => {
if (settingRes.authSetting['scope.record']) {
resolve()
}
},
})
}
}
})
}
},
})
})
},
// 頁面從前臺變?yōu)楹笈_時(shí)執(zhí)行
onHide: function () {
app.pageunBindOpEvent();
},
/**
* 生命周期函數(shù)--監(jiān)聽頁面卸載
*/
onUnload() {
const that = this;
app.pageunBindOpEvent();
that.data.innerAudioContext.destroy();//銷毀這個(gè)實(shí)例
},
})
/**
* 麥克風(fēng)幀動(dòng)畫
*/
function speaking() {
var that = this;
var delayTime = 1000;
var MAX_DURATION = 60000;
var COUNTDOWN_DURATION = 50000;
//話筒幀動(dòng)畫
var duration = 0;
that.timer = setInterval(function () {
duration = duration + delayTime;
console.log("duration==" + duration);
//倒計(jì)時(shí)提示-10秒
if (duration > COUNTDOWN_DURATION) {
var djs = parseInt((MAX_DURATION - duration) / 1000);
that.setData({
sendtip: '錄音倒計(jì)時(shí):' + djs + 's'
});
}
if (duration >= MAX_DURATION) {
that.handleStopVoice(that);
}
}, delayTime);
}
5、addmul.wxs文章來源:http://www.zghlxwxcb.cn/news/detail-401540.html
var filters = {
toFix2: function (value) {
return parseFloat(value).toFixed(2)//此處2為保留兩位小數(shù)
},
toFix1: function (value) {
return parseFloat(value).toFixed(1)//此處1為保留一位小數(shù)
},
toFix: function (value) {
return parseFloat(value).toFixed(0)//此處0為取整數(shù)
}
}
module.exports = {
toFix2: filters.toFix2,
toFix1: filters.toFix1,
toFix: filters.toFix
}
后續(xù)待補(bǔ)充
語音暫停,從原來暫停的位置開始播放文章來源地址http://www.zghlxwxcb.cn/news/detail-401540.html
到了這里,關(guān)于微信小程序-模仿繪制聊天界面的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!