search.vue 14 KB

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