voice.vue 12 KB

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