本文实现的功能:
配置答题库,可以发送文字或者语音提问,自动匹配问题的答案回答文字显示并且读出来。
相关文章:
5.微信小程序聊天功能 WebSocket 实现发送文字,图片,语音以及WebSocket 常见问题解决方案
6.[微信小程序]聊天对话(文本,图片)的功能(完整代码附效果图)
如果有个性化的需要改造,修改样式就行,可以帮助修改。
效果图:
发送文字:
发送语音:
有问题,可以评论留言或者微信联系我。
实现代码:
const app = getApp();
//引入插件:微信同声传译
const plugin = requirePlugin('WechatSI');
//获取全局唯一的语音识别管理器recordRecoManager
const manager = plugin.getRecordRecognitionManager();
const db = wx.cloud.database();
Page({
/**
* 页面的初始数据
*/
data: {
textInputTis:1,
tis: '长按发送语音',
//语音
recordState: false, //录音状态
content: '', //内容
answer: '', //答案
src: '', //答案语音
// 当is_clock为true时识别
is_clock: false,
list: [{
type: 2,
src: '',
text: '欢迎来到鄢电智能党务学习!',
}]
},
// 发送消息
sendOut(e) {
var that=this
console.log(1111, e)
let text = e.detail.value
if (text == '') {
wx.showToast({
title: '请输入聊天内容',
icon: 'none',
})
} else {
let arr = {
type: 1,
src: '',
text: text
};
var a = that.data.list;
a.push(arr)
that.setData({
content: text,
list: a
})
this.answer()
}
},
// 语音文字切换
textInputTis(){
console.log('this.data.textInputTis', this.data.textInputTis)
if (this.data.textInputTis==1){
this.setData({
textInputTis:2
})
}else if (this.data.textInputTis == 2) {
this.setData({
textInputTis: 1
})
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
//
//识别语音
this.initRecord();
},
onReady() {
//创建内部 audio 上下文 InnerAudioContext 对象。
this.innerAudioContext = wx.createInnerAudioContext();
this.innerAudioContext.onError(function (res) {
console.log(res);
wx.showToast({
title: '语音播放失败',
icon: 'none',
})
})
},
//识别语音 -- 初始化
initRecord: function () {
const that = this;
// 有新的识别内容返回,则会调用此事件
manager.onRecognize = function (res) {
console.log(res)
}
// 正常开始录音识别时会调用此事件
manager.onStart = function (res) {
console.log("成功开始录音识别", res)
}
// 识别错误事件
manager.onError = function (res) {
console.error("error msg", res)
}
//识别结束事件
manager.onStop = function (res) {
console.log('..............结束录音')
console.log('录音临时文件地址 -->' + res.tempFilePath);
console.log('录音总时长 -->' + res.duration + 'ms');
console.log('文件大小 --> ' + res.fileSize + 'B');
console.log('语音内容 --> ' + res.result);
if (res.result == '') {
wx.showModal({
title: '提示',
content: '听不清楚,请重新说一遍!',
showCancel: false,
success: function (res) { }
})
return;
}
var text = res.result;
text = text.replace(/[\ |\。|\~|\`|\!|\@|\#|\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\||\\|\[|\]|\{|\}|\;|\:|\"|\'|\,|\<|\.|\>|\/|\?]/g, "");
console.log('text', text)
let arr = {
type: 1,
src: res.tempFilePath,
text: res.result
};
var a = that.data.list;
a.push(arr)
console.log(333333333, a)
that.setData({
content: text,
list: a
})
that.answer();
that.bottom()
}
},
// 匹配答案
answer() {
// console.log('list',this.data.list)
var that = this
let content = this.data.content
db.collection("yuyin").where({
daan: {
$regex: '.*' + content + '.*',
$options: '1'
}
}).get({
success: res => {
console.log('匹配', res)
if (res.data.length == 0) {
wx.showModal({
title: '提示',
content: '暂时没找到对应答案',
showCancel: false,
success: function (res) { }
})
return;
}
that.setData({
answer: res.data[0].yuyin
})
this.wordYun()
}
})
},
bottom(){
var that = this;
this.setData({
scrollTop: 100000
})
},
// 答案文字转语音
wordYun: function (e) {
var that = this
var that = this;
let content = that.data.answer;
plugin.textToSpeech({
lang: "zh_CN",
tts: true,
content: content,
success: function (res) {
console.log(res);
console.log("succ tts", res.filename);
let arr = {
type: 2,
src: res.filename,
text: content
};
var a = that.data.list;
a.push(arr)
that.setData({
src: res.filename,
list: a,
})
that.yuyinPlay();
that.bottom()
},
fail: function (res) {
console.log("fail tts", res)
}
})
},
// 点击并播放当前语音
previewImage(e) {
console.log(e)
let src = e.currentTarget.dataset.src
this.innerAudioContext.src = src //设置音频地址
this.innerAudioContext.play(); //播放音频
},
//播放语音
yuyinPlay: function (e) {
if (this.data.src == '') {
console.log('暂无语音');
return;
}
this.innerAudioContext.src = this.data.src //设置音频地址
this.innerAudioContext.play(); //播放音频
},
//语音 --按住说话
touchStart: function (e) {
console.log('长按')
this.setData({
recordState: true, //录音状态
is_clock: true,
startPoint: e.touches[0], //记录触摸点的坐标信息
tis: '录音中...'
})
// 语音开始识别
manager.start({
lang: 'zh_CN', // 识别的语言,目前支持zh_CN en_US zh_HK sichuanhua
})
},
//语音 --松开结束
touchEnd: function (e) {
console.log('结束')
this.setData({
tis: '长按发送语音'
})
// 语音结束识别
manager.stop();
},
handleTouchMove: function (e) {
//计算距离,当滑动的垂直距离大于25时,则取消发送语音
if (Math.abs(e.touches[e.touches.length - 1].clientY - this.data.startPoint.clientY) > 25) {
this.setData({
is_clock: false //设置为不发送语音
})
}
},
})
WXML
<!--
<view>{{content}}</view>
<view>{{answer}}</view> -->
<view class="bo">
<scroll-view scroll-y="true" scroll-with-animation scroll-top="{{scrollTop}}">
<block >
<view class="body" wx:for='{{list}}' wx:key='index'>
<view wx:if='{{item.type==1}}' class="right_body">
<view class="flexRoe posRit">
<view class="textBo">
<view class="dataTime">{{item.sendOutname}} {{item.dataTime}}</view>
<view wx:if="{{item.text!=''}}" class="ritTxt">
<image wx:if="{{item.src!=''}}" bindtap="previewImage" mode='widthFix' data-src='{{item.src}}' src="../../img/voice.png" class="textImg"></image>
<view>{{item.text}}</view>
</view>
<view wx:if="{{item.voice}}" class="ritTxt2" bindtap='my_audio_click' data-src='{{item.voice}}'>
<image class='my_audio' src='/img/play.png'></image>
</view>
</view>
<image class="head" style=" margin-left:20rpx " src="../../img/1.png"></image>
</view>
</view>
<view wx:if='{{item.type==2}}' class="p_r left_body">
<view class="flexRoe ">
<image class="head" style=" margin-left:20rpx " src="../../img/2.png"></image>
<view class="lfBo">
<view class="dataTime">{{item.sendOutname}} {{item.dataTime}}</view>
<view wx:if="{{item.text!=''}}" class="lftTxt">
<image wx:if="{{item.src!=''}}" bindtap="previewImage" mode='widthFix' data-src='{{item.src}}' src="../../img/voice.png" class="textImg"></image>
<view>{{item.text}}</view>
</view>
<view wx:if="{{item.voice}}" class="lftTxt2" bindtap='my_audio_click' data-src='{{item.voice}}'>
<image class='my_audio' src='/img/play.png'></image>
</view>
</view>
</view>
</view>
</view>
</block>
</scroll-view>
<view class="jifBo" wx:if="{{tis=='录音中...'}}">
<image src="../../img/111.gif" mode="widthFix"></image>
</view>
<view class="inp">
<view class="p_r" wx:if="{{textInputTis==1}}">
<view class='serac_img' catch:longpress="touchStart" catch:touchmove="handleTouchMove" catch:touchend="touchEnd">
<view>{{tis}}</view>
<image src='../../img/voice.png' mode="widthFix"></image>
</view>
<view class="yuyin" bindtap="textInputTis">文字</view>
</view>
<view class="p_r" wx:if="{{textInputTis==2}}">
<input class="input" placeholder-class='plaCC' confirm-type="send" bindconfirm='sendOut' placeholder='聊天,在这里' value="{{title}}"></input>
<view class="yuyin" bindtap="textInputTis">语音</view>
</view>
</view>
</view>
CSS
page {
background: #f2f2f2;
}
.top_bo {
display: flex;
background: white;
flex-direction: row;
}
.top_bo view {
flex: 1;
font-size: 28rpx;
text-align: center;
border: 1rpx solid #ccc;
line-height: 80rpx;
}
scroll-view {
height: 90vh;
overflow: hidden;
padding-bottom: 30rpx;
padding-top:50rpx;
}
.noList {
background: white;
margin-top: 30rpx;
/* height: 230rpx; */
width: 100%;
/* padding-top: 25%; */
}
.noMsg {
text-align: center;
color: #999;
font-size: 28rpx;
}
.inp {
position: absolute;
bottom: 0;
width: 100%;
background: white;
height: 100rpx;
}
.inp input {
background: #d0d0d0;
border-radius: 15rpx;
padding-left: 20rpx;
height: 84rpx;
margin-top: 10rpx;
width: 75%;
margin-left: 2%;
}
.yuyin {
/* width: 60rpx */
background: #00a9ec;
border-radius: 15rpx;
height: 84rpx;
line-height: 84rpx;
color: #fff;
padding: 0 30rpx;
margin-left: 10rpx;
margin-top: 10rpx;
text-align: center;
}
.prohibit {
background: #d0d0d0;
border-radius: 15rpx;
padding-left: 20rpx;
height: 84rpx;
margin-top: 10rpx;
width: 94%;
margin-left: 2%;
text-align: center;
line-height: 84rpx;
color: rgb(34, 34, 34);
}
.plaCC {
margin-left: 5rpx;
}
.p_r {
display: flex;
flex-direction: row;
}
.flexRoe {
display: flex;
flex-direction: row;
}
.body {
width: 100%;
position: relative;
/* border: 1rpx solid #ccc; *//* height: 200rpx */
}
.right_body {
width: 100%;
/* border: 1rpx solid red; */
padding: 20rpx;
/* margin-top: 50rpx; */
}
.left_body {
/* margin-top: 50rpx; */
}
.posRit {
right: 20rpx;
}
.textBo {
width: 620rpx;
text-align: right;
}
.lfBo {
margin-left: 10rpx;
}
.ritTxt {
background: #44426a;
padding: 10rpx 20rpx 10rpx 20rpx;
color: white;
border-radius: 15rpx 0 15rpx 15rpx;
margin-top: 20rpx;
float: right;
max-width: 500rpx;
word-wrap: break-word;
text-align: left;
font-size: 28rpx;
}
.lftTxt {
background: #44426a;
padding: 10rpx 20rpx 10rpx 20rpx;
color: white;
border-radius: 0rpx 15rpx 15rpx 15rpx;
margin-top: 20rpx;
float: left;
max-width: 500rpx;
word-wrap: break-word;
text-align: left;
font-size: 28rpx;
}
.my_audio {
height: 60rpx;
width: 60rpx;
z-index: 2;
position: relative;
top: 10rpx;
left: 20rpx;
margin-right: 30rpx;
}
.dataTime {
font-size: 28rpx;
}
.textImg {
width: 50rpx;
margin-top: 5rpx;
}
.head {
width: 80rpx;
height: 80rpx;
}
._ {
height: 130rpx;
width: 100%;
background: #ccc;
}
.serac_img {
width: 75%;
display: flex;
flex-direction: row;
background: #d0d0d0;
border-radius: 15rpx;
padding-left: 20rpx;
/* height: 84rpx; */
margin-top: 10rpx;
margin-left: 2%;
}
.serac_img view {
width: 70%;
text-align: right;
line-height: 80rpx;
color: #bfbfbf;
}
.serac_img image {
width: 50rpx;
margin-top: 25rpx;
}
.jifBo{
position: absolute;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.5);
width: 100%;
height: 100%;
}
.jifBo image{
width: 40%;
margin-left: 30%;
margin-top: 50%;
}