voice.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. <template>
  2. <view>
  3. <view class="bgbox" v-if="voiceflag" @click="getClose"></view>
  4. <view class="voice" v-if="voiceflag">
  5. <image :src="closeimg" class="choseimg" @click="getClose"></image>
  6. <view v-if="isShow">
  7. <view v-if="sendLock" class="tip">
  8. <view class="txt">试试这样说</view>
  9. <view class="txt-bt">科技</view>
  10. </view>
  11. <view v-else>
  12. <!-- @click="resultClick" -->
  13. <view class="res-txt" >
  14. <text :style="{
  15. color: (resultText == '正在识别中2...' || resultText == '未检测到语音,请重试') ?
  16. '#919098' :
  17. '#2979ff'}">
  18. {{resultText}}
  19. </text>
  20. <!-- <image v-if="resultText != '正在识别中.1..' && resultText != '未检测到语音,请重试'"
  21. src="/static/img/xiaoshou.png" mode="widthFix"></image> -->
  22. </view>
  23. </view>
  24. </view>
  25. <view v-else class="tip">
  26. <view v-if="!sendLock" class="tipbox">{{voicetext}}</view>
  27. <view v-html="text" class="txt" style="color: #8e8d9a;"></view>
  28. <view v-if="!sendLock" class="prompt-loader">
  29. <!-- #ifndef MP-WEIXIN -->
  30. <view class="em" :style="randomRgb()" v-for="(item,index) in 30" :key="index"></view>
  31. <!-- #endif -->
  32. <!-- #ifdef MP-WEIXIN -->
  33. <view class="em" :style="'background-color:' +colors[index]+';'" v-for="(item,index) in 30" :key="index"></view>
  34. <!-- #endif -->
  35. </view>
  36. <view v-else class="prompt-loader"></view>
  37. </view>
  38. <view class="btn" @longpress="handleRecordStart" @touchmove="handleTouchMove" @touchend="handleRecordStop">
  39. <view class="btn-cont">
  40. 长按开始语音搜索
  41. </view>
  42. </view>
  43. </view>
  44. </view>
  45. </template>
  46. <script>
  47. const recorderManager = uni.getRecorderManager();
  48. //播放录音
  49. const innerAudioContext = uni.createInnerAudioContext();
  50. innerAudioContext.autoplay = true;
  51. export default{
  52. props:{
  53. voiceflag:{
  54. type: Boolean,
  55. default () {
  56. return false
  57. }
  58. }
  59. },
  60. watch:{
  61. sendLock(newVal, oldVal) {
  62. var that=this;
  63. recorderManager.onStop(res => {
  64. if (newVal) return //上锁不发送
  65. //解锁发送网络请求
  66. setTimeout(function(res){
  67. if(!that.voicetext&&!that.partialResult){
  68. that.resultText='未检测到语音,请重试';
  69. that.sendLock=true;
  70. }else{
  71. that.resultText=that.voicetext||that.partialResult;
  72. // console.log(that.resultText,85)
  73. // that.xmmc=that.xmmc+that.resultText;
  74. setTimeout(function(){
  75. that.$emit('getVoice',that.resultText)
  76. // that.voiceflag=false;
  77. that.sendLock=true;
  78. },1200)
  79. }
  80. },1000)
  81. // console.log(res.tempFilePath, '获取录音文件')
  82. });
  83. },
  84. },
  85. data(){
  86. return {
  87. // speechimg:require('@/static/images/index/speech.png'),
  88. xmmc:'',
  89. closeimg:require("@/static/images/close.png"),
  90. // voiceflag:true,
  91. voiceToken: '',
  92. timer: null,
  93. text: '',
  94. resultText: '正在识别中...',
  95. startPoint: {},
  96. sendLock: true,
  97. isShow: true,
  98. adioFileData: '',
  99. adioSize: '',
  100. resContent: '',
  101. luyinStatus: true,
  102. voicetext:'',
  103. options: {}, // 语音转文字的设置
  104. partialResult:'',//临时语音
  105. colors: [],
  106. }
  107. },
  108. onLoad: function() {
  109. const colors = [];
  110. for (let i = 0; i < 30; i++) { // 假设你想为30个元素生成颜色
  111. colors.push(this.randomRgb());
  112. }
  113. this.setData({
  114. colors: colors
  115. });
  116. },
  117. mounted() {
  118. // if(!this.xmmc){
  119. // this.xmmc=this.name
  120. // }
  121. var that=this;
  122. // #ifdef APP-PLUS
  123. // 监听语音识别事件
  124. plus.speech.addEventListener('start', this.ontStart, false);
  125. plus.speech.addEventListener('volumeChange', this.onVolumeChange, false);
  126. plus.speech.addEventListener('recognizing', this.onRecognizing, false);
  127. plus.speech.addEventListener('recognition', this.onRecognition, false);
  128. plus.speech.addEventListener('end', this.onEnd, false);
  129. // #endif
  130. recorderManager.onStop(function(res) {
  131. //录音后的回调函数
  132. // console.log('recorder stop' + JSON.stringify(res));
  133. // console.log(res.tempFilePath);
  134. that.voicePath = res.tempFilePath;
  135. // self.voicePath =
  136. });
  137. },
  138. onUnload() {
  139. },
  140. unmounted() {
  141. },
  142. methods:{
  143. getClose(){
  144. // this.voiceflag=false;
  145. this.sendLock=true;
  146. this.$emit('getClose')
  147. },
  148. onRecognizing(e){
  149. this.partialResult=this.partialResult+e.partialResult;
  150. },
  151. // 录音转文字
  152. handleVoice() {
  153. // console.log('语音输入')
  154. let _this = this;
  155. this.options.engine = 'baidu'
  156. // this.options.timeout = 60 * 1000; //超时时间
  157. this.options.continue = true;//语音识别是否采用持续模式
  158. this.options.punctuation = false; // 是否需要标点符号
  159. this.options.userInterface = false; // 是否显示语音界面
  160. plus.speech.startRecognize(this.options, (s) => {
  161. console.log(s,1)
  162. _this.voicetext+=s;
  163. // plus.speech.stopRecognize(); // 关
  164. });
  165. },
  166. //长按录音方法
  167. handleRecordStart(e) {
  168. this.voicetext='';
  169. this.partialResult='';
  170. this.startPoint = e.touches[0]; //记录长按时开始点信息,后面用于计算上划取消时手指滑动的距离。
  171. recorderManager.start({duration: 60000}); //开始录音
  172. this.handleVoice()
  173. this.text = `<text style="color:#333">上划取消识别</text>`;
  174. this.sendLock = false; //长按时不上锁。
  175. this.isShow = false;
  176. this.resultText = '正在识别中...';
  177. // 按钮
  178. },
  179. //结束录音 (手指松开)时触发
  180. handleRecordStop(e) {
  181. var that=this;
  182. this.isShow = true;
  183. setTimeout(function(){
  184. plus.speech.stopRecognize();
  185. recorderManager.stop(); //结束录音
  186. },1200)
  187. },
  188. //上划取消搜索
  189. handleTouchMove(e) {
  190. let moveLenght = e.touches[e.touches.length - 1].clientY - this.startPoint.clientY;
  191. if (Math.abs(moveLenght) > 50) {
  192. this.text = `松开手指,<text style="color:#333">取消搜索</text>`;
  193. this.sendLock = true; //触发了上滑取消搜索,上锁
  194. this.isShow = false;
  195. } else {
  196. this.text = `<text style="color:#333">上划取消搜索</text>`;
  197. this.sendLock = false; //上划距离不足,可以搜索,不上锁
  198. this.isShow = false;
  199. }
  200. },
  201. //获取录音结果子传父
  202. resultClick() {
  203. if (this.resultText == '正在识别中...' || this.resultText == '未检测到语音,请重试') return;
  204. this.$emit('getVoice',this.resultText)
  205. // this.voiceflag=false;
  206. this.sendLock=true;
  207. // this.$emit('voiceResult', this.resultText)
  208. },
  209. //弹窗关闭之后的操作,点击遮罩层或关闭按钮
  210. // afterHide() {
  211. // this.sendLock = true;
  212. // this.$emit('closePopup');
  213. // clearInterval(this.timer);
  214. // this.resultText = '正在识别中...';
  215. // },
  216. randomRgb() {
  217. let R = Math.floor(Math.random() * 130 + 110);
  218. let G = Math.floor(Math.random() * 130 + 110);
  219. let B = Math.floor(Math.random() * 130 + 110);
  220. return `rgb(${R},${G},${B}, 1)`
  221. // background: `rgb(${R},${G},${B}, 1)`
  222. },
  223. // 语音转文字
  224. getSeep(){
  225. this.voicetext='';
  226. this.partialResult='';
  227. this.voiceflag=true;
  228. },
  229. onEnd() {
  230. // let routes = getCurrentPages(); // 获取当前打开过的页面路由数组
  231. // let curRoute = routes[routes.length - 1].route //获取当前页面路由
  232. // if(curRoute=='pages/work/shprogress'){
  233. // }else{
  234. // if(this.xmmc){
  235. // this.getVoice()
  236. // }
  237. // }
  238. },
  239. }
  240. }
  241. </script>
  242. <style lang="scss" scoped>
  243. // 搜索
  244. .listtopa{border: 6rpx solid #FD5001;border-radius: 32rpx;height:64rpx;box-sizing: border-box;padding:0 140rpx 0 32rpx ;position: relative;
  245. input{}
  246. image{width: 20rpx;height: 30rpx;margin-right: 16rpx;}
  247. .btn{background: #FA5F03;border-radius: 32rpx;width: 120rpx;position: absolute;right: -2rpx;top: -2rpx;bottom:-2rpx;}
  248. }
  249. .bgbox{z-index: 1900;}
  250. .choseimg{width: 34rpx;height: 34rpx;position: absolute;left: 36rpx;top: 54rpx;}
  251. .voice {
  252. min-height: 500rpx;
  253. padding: 100rpx 60rpx 0 60rpx;
  254. position: relative;
  255. background-color: #fff;
  256. position: fixed;
  257. left:0;right:0;bottom:0;z-index: 2000;
  258. padding-bottom: 180rpx;
  259. .res-txt {
  260. text-align: center;
  261. margin-top: 40rpx;
  262. font-size: 36rpx;
  263. color: #919098;
  264. image {
  265. display: block;
  266. margin: auto;
  267. margin-top: 10rpx;
  268. width: 60rpx;
  269. animation: bounce-down 2.6s linear infinite;
  270. }
  271. }
  272. .tip {
  273. margin-top: 15rpx;
  274. text-align: center;
  275. .txt {
  276. font-size: 36rpx;
  277. color: #151823;
  278. }
  279. .txt-bt {
  280. margin-top: 20rpx;
  281. color: #919098;
  282. }
  283. }
  284. .btn {
  285. width: 50%;
  286. height: 80rpx;
  287. display: flex;
  288. align-items: center;
  289. justify-content: center;
  290. color: #fff;
  291. border-radius: 50rpx;
  292. background: #3484fd;
  293. position: absolute;
  294. bottom: 80rpx;
  295. left: 50%;
  296. transform: translateX(-50%);
  297. .btn-cont {
  298. display: flex;
  299. align-items: center;
  300. }
  301. }
  302. }
  303. @-webkit-keyframes bounce-down {
  304. 25% {
  305. -webkit-transform: translateY(-10px);
  306. }
  307. 50%,
  308. 100% {
  309. -webkit-transform: translateY(0);
  310. }
  311. 75% {
  312. -webkit-transform: translateY(13px);
  313. }
  314. }
  315. .content{background-color: #f5f5f5;position: fixed;left: 0;right: 0;bottom: 0;z-index: 10000;height: 500rpx;}
  316. /* 语音动画 */
  317. .prompt-loader {
  318. width: 100%;
  319. height: 35px;
  320. display: flex;
  321. align-items: center;
  322. justify-content: space-between;
  323. margin: 30rpx auto;
  324. }
  325. .prompt-loader .em {
  326. height: 15%;
  327. width: 2px;
  328. float: left;
  329. display: block;
  330. background: #333333;
  331. }
  332. .prompt-loader .em:last-child {
  333. margin-right: 0px;
  334. }
  335. .prompt-loader .em:nth-child(1) {
  336. animation: load 1.3s 0.4s infinite linear;
  337. }
  338. .prompt-loader .em:nth-child(2) {
  339. animation: load 1.3s 0.2s infinite linear;
  340. }
  341. .prompt-loader .em:nth-child(3) {
  342. animation: load 1.3s 0.6s infinite linear;
  343. }
  344. .prompt-loader .em:nth-child(4) {
  345. animation: load 1.3s 0.8s infinite linear;
  346. }
  347. .prompt-loader .em:nth-child(5) {
  348. animation: load 1.3s 0.6s infinite linear;
  349. }
  350. .prompt-loader .em:nth-child(6) {
  351. animation: load 1.3s 0.4s infinite linear;
  352. }
  353. .prompt-loader .em:nth-child(7) {
  354. animation: load 1.3s 0.2s infinite linear;
  355. }
  356. .prompt-loader .em:nth-child(8) {
  357. animation: load 1.3s 0.6s infinite linear;
  358. }
  359. .prompt-loader .em:nth-child(9) {
  360. animation: load 1.3s 0.2s infinite linear;
  361. }
  362. .prompt-loader .em:nth-child(10) {
  363. animation: load 1.3s 0.4s infinite linear;
  364. }
  365. .prompt-loader .em:nth-child(11) {
  366. animation: load 1.3s 0.6s infinite linear;
  367. }
  368. .prompt-loader .em:nth-child(12) {
  369. animation: load 1.3s 0.8s infinite linear;
  370. }
  371. .prompt-loader .em:nth-child(13) {
  372. animation: load 1.3s 1s infinite linear;
  373. }
  374. .prompt-loader .em:nth-child(14) {
  375. animation: load 1.3s 0.2s infinite linear;
  376. }
  377. .prompt-loader .em:nth-child(15) {
  378. animation: load 1.3s 0.6s infinite linear;
  379. }
  380. .prompt-loader .em:nth-child(16) {
  381. animation: load 1.3s 0.6s infinite linear;
  382. }
  383. .prompt-loader .em:nth-child(17) {
  384. animation: load 1.3s 0.8s infinite linear;
  385. }
  386. .prompt-loader .em:nth-child(18) {
  387. animation: load 1.3s 0.2s infinite linear;
  388. }
  389. .prompt-loader .em:nth-child(19) {
  390. animation: load 1.3s 0.4s infinite linear;
  391. }
  392. .prompt-loader .em:nth-child(20) {
  393. animation: load 1.3s 0.6s infinite linear;
  394. }
  395. .prompt-loader .em:nth-child(21) {
  396. animation: load 1.3s 0.5s infinite linear;
  397. }
  398. .prompt-loader .em:nth-child(22) {
  399. animation: load 1.3s 0.2s infinite linear;
  400. }
  401. .prompt-loader .em:nth-child(23) {
  402. animation: load 1.3s 0.4s infinite linear;
  403. }
  404. .prompt-loader .em:nth-child(24) {
  405. animation: load 1.3s 0.6s infinite linear;
  406. }
  407. .prompt-loader .em:nth-child(25) {
  408. animation: load 1.3s 0.8s infinite linear;
  409. }
  410. .prompt-loader .em:nth-child(26) {
  411. animation: load 1.3s 0.2s infinite linear;
  412. }
  413. .prompt-loader .em:nth-child(27) {
  414. animation: load 1.3s 0.4s infinite linear;
  415. }
  416. .prompt-loader .em:nth-child(28) {
  417. animation: load 1.3s 0.1s infinite linear;
  418. }
  419. .prompt-loader .em:nth-child(29) {
  420. animation: load 1.3s 0.3s infinite linear;
  421. }
  422. .prompt-loader .em:nth-child(30) {
  423. animation: load 1.3s 0.6s infinite linear;
  424. }
  425. @keyframes load {
  426. 0% {
  427. height: 15%;
  428. }
  429. 50% {
  430. height: 100%;
  431. }
  432. 100% {
  433. height: 15%;
  434. }
  435. }
  436. </style>