addVoice.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. <template>
  2. <view class="add-voice-page">
  3. <van-field
  4. :value="form.title"
  5. placeholder="请输入语音标题"
  6. :border="false"
  7. clearable
  8. @change="inputChange"
  9. label="语音标题"
  10. />
  11. <van-field
  12. :value="form.variety_name"
  13. placeholder="请选择品种"
  14. :border="false"
  15. readonly
  16. is-link
  17. label="品种"
  18. @click-input="showFilter=true"
  19. @click-icon="showFilter=true"
  20. />
  21. <van-field
  22. :value="form.section_name"
  23. placeholder="请选择板块名称"
  24. :border="false"
  25. readonly
  26. is-link
  27. label="板块名称"
  28. @click-input="showFilter=true"
  29. @click-icon="showFilter=true"
  30. />
  31. <view class="flex audio-box" v-if="recorderStatus==='stop'">
  32. <image
  33. :src="temAudio.paused?'../../../static/voice/pause.png':'../../../static/voice/playing.png'"
  34. mode="aspectFill"
  35. @click="handlePlayAudio"
  36. />
  37. <slider
  38. activeColor="#E6B77D"
  39. :max="temAudio.duration"
  40. :value="temAudio.curTime"
  41. @change="handleAudioSliderChange($event)"
  42. block-size="12"
  43. class="slider"
  44. />
  45. <text class="left-time">{{temAudio.curTime|formatTime}}</text>
  46. <text class="right-time">{{temAudio.duration|formatTime}}</text>
  47. <view class="del-btn" @click="handleDelRecord">
  48. <image src="./static/del.png" mode="aspectFill" />
  49. <text>删除</text>
  50. </view>
  51. </view>
  52. <view class="empty-voice-box" v-if="recorderStatus==='start'">
  53. <image src="./static/record.png" mode="aspectFill" />
  54. <view>无录音(录音时长超过十分钟自动结束)</view>
  55. </view>
  56. <view class="animat-box" v-if="recorderStatus==='doing'||recorderStatus==='pause'">
  57. <view class="con-box">
  58. <image :class="['img move1',recorderStatus==='doing'?'animat-run':'animat-pause']" src="./static/record-img.png" mode="widthFix" />
  59. <image :class="['img move2',recorderStatus==='doing'?'animat-run':'animat-pause']" src="./static/record-img.png" mode="widthFix" />
  60. <image :class="['img move3',recorderStatus==='doing'?'animat-run':'animat-pause']" src="./static/record-img.png" mode="widthFix" />
  61. </view>
  62. <view class="bot-text">{{time|formatTime}}</view>
  63. </view>
  64. <view class="sound-record-wrap" v-if="recorderStatus!=='stop'">
  65. <view class="top-text">点击开始录音</view>
  66. <image class="btn" :src="btnImg" mode="aspectFill" @click="handleClickBtn" />
  67. <view
  68. class="del-btn"
  69. :style="{color:recorderStatus==='doing'&&'#999'}"
  70. v-if="recorderStatus!=='start'"
  71. @click="handleResetRecorder"
  72. >删除</view>
  73. <view
  74. class="done-btn"
  75. v-if="recorderStatus!=='start'"
  76. @click="handleEndRecorder"
  77. >完成</view>
  78. </view>
  79. <view class="publish-btn" v-if="recorderStatus==='stop'" @click="handlePublish">发布</view>
  80. <!-- 筛选弹窗 -->
  81. <van-popup
  82. :show="showFilter"
  83. position="bottom"
  84. :close-on-click-overlay="true"
  85. @close="showFilter = false"
  86. round
  87. >
  88. <view class="fliter-wrap-list">
  89. <view class="flex top">
  90. <text style="color:#000">全部选项</text>
  91. <text style="color:#E3B377" @click="showFilter=false">取消</text>
  92. </view>
  93. <van-tree-select
  94. :items="options"
  95. :main-active-index="mainActiveIndex"
  96. :active-id="activeId"
  97. @click-nav="onClickNav"
  98. @click-item="onClickItem"
  99. main-active-class="main-active-class"
  100. content-active-class="content-active-class"
  101. />
  102. </view>
  103. </van-popup>
  104. </view>
  105. </template>
  106. <script>
  107. import {apiVoiceSectionList} from '@/api/voice'
  108. import {baseApiUrl} from '@/utils/config.js'
  109. import CryptoJS from '@/utils/crypto.js'
  110. import uniAsync from "@/utils/uni-async.js"; // uni api async 化
  111. const recorderManager = wx.getRecorderManager();//录音实例
  112. let innerAudioContext = uni.createInnerAudioContext();//播放音频实例
  113. let TIMER=null//计时器
  114. export default {
  115. filters:{
  116. formatTime(e){
  117. let m=parseInt(e/60)
  118. let s=parseInt(e%60)
  119. return `${m>9?m:'0'+m}:${s>9?s:'0'+s}`
  120. }
  121. },
  122. computed:{
  123. btnImg(){
  124. if(this.recorderStatus==='start'){
  125. return './static/voice-start.png'
  126. }else if(this.recorderStatus==='doing'){
  127. return './static/voice-doing.png'
  128. }else if(this.recorderStatus==='stop'){
  129. return './static/voice-pause.png'
  130. }else if(this.recorderStatus==='pause'){
  131. return './static/voice-pause.png'
  132. }
  133. }
  134. },
  135. data() {
  136. return {
  137. form:{
  138. title:'',//语音标题
  139. variety_name:'',
  140. variety_id:'',
  141. section_id:'',
  142. section_name:'',
  143. img_url:''
  144. },
  145. recorderStatus:'start',//当前录音状态 start开始 doing正在录音 stop停止录音 pause录音暂停
  146. time:0,
  147. isReset:false,//是否点击了重置
  148. temAudio:{
  149. url:'',//临时音频地址
  150. duration:'',//时长
  151. size:'',//大小
  152. curTime:0,//播放时当前播放的时间
  153. paused:true,
  154. },//临时音频文件信息
  155. showFilter:false,
  156. options:[],
  157. mainActiveIndex:0,
  158. activeId:0,//选择的板块id
  159. }
  160. },
  161. onLoad(){
  162. // 调取用户授权使用麦克风
  163. uni.authorize({
  164. scope: 'scope.record',
  165. success() {}
  166. })
  167. this.listenVoice()
  168. this.getOptionsList()
  169. },
  170. onShow(){
  171. innerAudioContext = uni.createInnerAudioContext()
  172. this.listenAudio()
  173. },
  174. onHide(){
  175. innerAudioContext.destroy()
  176. this.temAudio.paused=true
  177. },
  178. onUnload(){
  179. innerAudioContext.destroy()
  180. },
  181. methods: {
  182. //录音事件
  183. listenVoice(){
  184. recorderManager.onStart(()=>{
  185. //录音开始监听事件
  186. console.log('开始录音');
  187. this.recorderStatus='doing'
  188. this.isReset=false
  189. if(!TIMER){
  190. TIMER=setInterval(() => {
  191. this.time++
  192. }, 1000);
  193. }
  194. })
  195. recorderManager.onPause(()=>{
  196. //录音暂停监听事件
  197. console.log('录音暂停');
  198. this.recorderStatus='pause'
  199. clearInterval(TIMER)
  200. TIMER=null
  201. })
  202. recorderManager.onResume(()=>{
  203. //录音继续监听事件
  204. console.log('录音继续');
  205. this.recorderStatus='doing'
  206. if(!TIMER){
  207. TIMER=setInterval(() => {
  208. this.time++
  209. }, 1000);
  210. }
  211. })
  212. recorderManager.onStop((e)=>{
  213. //录音结束监听事件
  214. console.log('录音结束',e);
  215. // 如果是点击重置(删除按钮)的 则不做结束处理
  216. if(!this.isReset){
  217. this.recorderStatus='stop'
  218. this.temAudio.url=e.tempFilePath
  219. this.temAudio.size=e.fileSize
  220. this.temAudio.duration=parseInt(e.duration/1000)
  221. }
  222. clearInterval(TIMER)
  223. TIMER=null
  224. })
  225. recorderManager.onError((e)=>{
  226. //录音事件错误监听
  227. console.log('录音错误哦',e);
  228. })
  229. },
  230. //点击录音操作按钮
  231. async handleClickBtn(){
  232. const setRes=await uniAsync.getSetting()
  233. console.log(setRes.authSetting['scope.record']);
  234. if(!setRes.authSetting['scope.record']){
  235. uni.showToast({
  236. title:'请打开麦克风交流',
  237. icon:'none'
  238. })
  239. uni.openSetting()
  240. return
  241. }
  242. if(this.recorderStatus==='start'){
  243. recorderManager.start({
  244. duration:600000,
  245. format:'mp3'
  246. })
  247. }
  248. if(this.recorderStatus==='doing'){
  249. recorderManager.pause()//暂停录音
  250. }
  251. if(this.recorderStatus==='pause'){
  252. recorderManager.resume()//继续录音
  253. }
  254. },
  255. //点击重置录音状态
  256. handleResetRecorder(){
  257. if(this.recorderStatus==='doing') return
  258. this.isReset=true
  259. recorderManager.stop()
  260. this.recorderStatus='start'
  261. this.handleDelRecord()
  262. },
  263. //点击完成录音
  264. handleEndRecorder(){
  265. //点击完成时还不是已结束录音状态
  266. this.isReset=false
  267. recorderManager.stop()
  268. },
  269. //拖动音频播放进度条
  270. handleAudioSliderChange(e){
  271. const value=e.detail.value
  272. innerAudioContext.seek(value)
  273. },
  274. //点击播放\暂停音频
  275. handlePlayAudio(){
  276. innerAudioContext.src=this.temAudio.url
  277. if(this.temAudio.paused){
  278. innerAudioContext.play()
  279. }else{
  280. innerAudioContext.pause()
  281. }
  282. },
  283. //音频播放事件
  284. listenAudio(){
  285. innerAudioContext.onPlay(()=>{
  286. console.log('开始播放录音');
  287. this.temAudio.paused=false
  288. })
  289. innerAudioContext.onPause(()=>{
  290. console.log('录音播放暂停');
  291. this.temAudio.paused=true
  292. })
  293. // innerAudioContext.onStop(()=>{
  294. // console.log('录音播放停止');
  295. // this.temAudio.paused=true
  296. // innerAudioContext.src=''
  297. // })
  298. innerAudioContext.onEnded(()=>{
  299. console.log('录音播放自然结束');
  300. this.temAudio.curTime=this.temAudio.duration
  301. setTimeout(() => {
  302. this.temAudio.paused=true
  303. innerAudioContext.src=''
  304. this.temAudio.curTime=0
  305. }, 300);
  306. })
  307. innerAudioContext.onTimeUpdate(()=>{
  308. this.temAudio.curTime=parseInt(innerAudioContext.currentTime)
  309. })
  310. },
  311. //删除录音记录
  312. handleDelRecord(){
  313. this.recorderStatus='start'
  314. this.isReset=true
  315. this.temAudio.url=''
  316. this.temAudio.duration=''
  317. this.temAudio.curTime=0
  318. this.temAudio.paused=true
  319. this.time=0
  320. innerAudioContext.stop()
  321. TIMER=null
  322. },
  323. //获取选项数据
  324. async getOptionsList(){
  325. const res=await apiVoiceSectionList()
  326. if(!res.code===200) return
  327. const arr=res.data||[]
  328. this.options=arr.filter(item=>{
  329. item.text=item.VarietyName
  330. item.children=item.Children.filter(_item=>{
  331. if(_item.Status===1){
  332. _item.text=_item.SectionName
  333. _item.id=_item.SectionId
  334. return _item
  335. }
  336. })
  337. if(item.children.length>0){
  338. delete item.Children
  339. return item
  340. }
  341. })
  342. },
  343. onClickNav({detail}){
  344. console.log(detail);
  345. this.mainActiveIndex=detail.index
  346. },
  347. onClickItem({detail}){
  348. console.log(detail);
  349. this.activeId=detail.id
  350. this.form.section_id=detail.SectionId
  351. this.form.section_name=detail.SectionName
  352. this.form.variety_name=this.options[this.mainActiveIndex].VarietyName
  353. this.form.variety_id=this.options[this.mainActiveIndex].VarietyId
  354. this.form.img_url=detail.ImgUrl
  355. this.showFilter=false
  356. },
  357. inputChange(event) {
  358. this.form.title=event.detail
  359. },
  360. // 发布
  361. handlePublish(){
  362. if(!this.form.title||!this.form.variety_id){
  363. uni.showToast({
  364. title:'请将内容填写完整',
  365. icon:'none'
  366. })
  367. return
  368. }
  369. let formData={
  370. broadcast_name:this.form.title,
  371. section_id:Number(this.form.section_id),
  372. section_name:this.form.section_name,
  373. variety_id:Number(this.form.variety_id),
  374. variety_name:this.form.variety_name,
  375. img_url:this.form.img_url,
  376. author_id:Number(this.$store.state.user.userInfo.user_id),
  377. author:this.$store.state.user.userInfo.real_name
  378. }
  379. uni.uploadFile({
  380. url: baseApiUrl + "/voice/broadcast/add",
  381. filePath: this.temAudio.url,
  382. name: 'file',
  383. header: {
  384. Authorization: this.$store.state.user.token,
  385. },
  386. formData: formData,
  387. success: (result) => {
  388. const { envVersion } = uni.getAccountInfoSync().miniProgram
  389. const res = envVersion === 'release' ? JSON.parse(CryptoJS.Des3Decrypt(result.data)) : JSON.parse(result.data);
  390. console.log(res);
  391. if(res.code===200){
  392. uni.showToast({
  393. title:'发布成功',
  394. icon:'success'
  395. })
  396. setTimeout(() => {
  397. uni.$emit('addVoiceSuccess')
  398. uni.navigateBack()
  399. }, 1000);
  400. }else{
  401. uni.showToast({
  402. title:res.msg,
  403. icon:'none'
  404. })
  405. }
  406. },
  407. fail: () => {
  408. console.log('发布失败');
  409. uni.showToast({
  410. title:'发布失败,请稍后重试!',
  411. icon:'none'
  412. })
  413. },
  414. complete: () => {
  415. console.log('con');
  416. }
  417. });
  418. }
  419. },
  420. }
  421. </script>
  422. <style lang="scss">
  423. .add-voice-page .van-cell{
  424. border-bottom: 1px solid #e5e5e5;
  425. }
  426. .add-voice-page .van-field__label{
  427. font-size: 32rpx;
  428. color: #333;
  429. }
  430. /* .add-voice-page .van-cell__title{
  431. max-width: 6.2em;
  432. min-width: 6.2em;
  433. margin-right: 12px;
  434. font-size: 32rpx;
  435. color: #333;
  436. } */
  437. .add-voice-page .van-cell__value{
  438. text-align: left;
  439. font-size: 32rpx;
  440. }
  441. .add-voice-page{
  442. .fliter-wrap-list{
  443. background-color: #fff;
  444. padding-top: 53rpx;
  445. .top{
  446. font-size: 32rpx;
  447. justify-content: space-between;
  448. margin-bottom: 40rpx;
  449. padding: 0 34rpx;
  450. }
  451. .van-sidebar{
  452. flex-shrink: 0;
  453. }
  454. .van-tree-select__content{
  455. overflow-x: hidden;
  456. }
  457. .main-active-class{
  458. border-color: #E3B377;
  459. }
  460. .content-active-class{
  461. color: #E3B377;
  462. }
  463. }
  464. }
  465. page{
  466. padding-bottom:constant(safe-area-inset-bottom);
  467. padding-bottom:env(safe-area-inset-bottom);
  468. }
  469. </style>
  470. <style lang="scss" scoped>
  471. .add-voice-page{
  472. .empty-voice-box{
  473. height: 50vh;
  474. padding-top: 150rpx;
  475. image{
  476. width: 140rpx;
  477. height: 140rpx;
  478. margin-bottom: 20rpx;
  479. }
  480. text-align: center;
  481. color: #999999;
  482. }
  483. .sound-record-wrap{
  484. border-top: 1px solid #E6E6E6;
  485. padding-top: 126rpx;
  486. position: relative;
  487. .top-text{
  488. text-align: center;
  489. color: #EE3636;
  490. position: absolute;
  491. top: 50rpx;
  492. left: 50%;
  493. transform: translateX(-50%);
  494. }
  495. .btn{
  496. width: 118rpx;
  497. height: 118rpx;
  498. display: block;
  499. margin-left: auto;
  500. margin-right: auto;
  501. }
  502. .del-btn{
  503. position: absolute;
  504. left: 122rpx;
  505. bottom: 40rpx;
  506. font-size: 32rpx;
  507. color: #EE3636;
  508. }
  509. .done-btn{
  510. position: absolute;
  511. right: 122rpx;
  512. bottom: 40rpx;
  513. font-size: 32rpx;
  514. color: #EE3636;
  515. }
  516. }
  517. .audio-box{
  518. background-color: #FDF8F2;
  519. height: 123rpx;
  520. align-items: center;
  521. margin-left: 34rpx;
  522. margin-right: 34rpx;
  523. margin-top: 60rpx;
  524. margin-bottom: 180rpx;
  525. padding: 0 30rpx;
  526. position: relative;
  527. .left-time{
  528. position: absolute;
  529. bottom: 20rpx;
  530. left: 100rpx;
  531. color: #999999;
  532. font-size: 20rpx;
  533. }
  534. .right-time{
  535. position: absolute;
  536. bottom: 20rpx;
  537. right: 40rpx;
  538. color: #999999;
  539. font-size: 20rpx;
  540. }
  541. image{
  542. width: 40rpx;
  543. height: 48rpx;
  544. flex-shrink: 0;
  545. margin-right: 30rpx;
  546. }
  547. .slider{
  548. flex: 1;
  549. margin: 0 10rpx;
  550. }
  551. .del-btn{
  552. display: flex;
  553. align-items: center;
  554. color: #999999;
  555. font-size: 28rpx;
  556. position: absolute;
  557. bottom: -50rpx;
  558. right: 0;
  559. image{
  560. width: 32rpx;
  561. height: 35rpx;
  562. margin-right: 10rpx;
  563. }
  564. }
  565. }
  566. .publish-btn{
  567. width: 390rpx;
  568. height: 80rpx;
  569. text-align: center;
  570. line-height: 80rpx;
  571. color: #fff;
  572. background: #E6B77D;
  573. font-size: 32rpx;
  574. border-radius: 40px;
  575. margin-left: auto;
  576. margin-right: auto;
  577. }
  578. }
  579. .animat-box{
  580. height: 50vh;
  581. .con-box{
  582. height: 80%;
  583. background-color: #FAFAFA;
  584. position: relative;
  585. }
  586. .bot-text{
  587. font-size: 60rpx;
  588. padding-top: 36rpx;
  589. text-align: center;
  590. }
  591. .img{
  592. position: absolute;
  593. width: 100vw;
  594. top: 27%;
  595. transform: translateX(100vw);
  596. }
  597. .move1{
  598. animation: move 30s linear infinite;
  599. }
  600. .move2{
  601. animation: move 30s 10s linear infinite;
  602. }
  603. .move3{
  604. animation: move 30s 20s linear infinite;
  605. }
  606. .animat-pause{
  607. animation-play-state: paused;
  608. }
  609. .animat-run{
  610. animation-play-state: running;
  611. }
  612. @keyframes move {
  613. 0%{
  614. transform: translateX(100vw);
  615. }
  616. 100%{
  617. transform: translateX(-200vw);
  618. }
  619. }
  620. }
  621. </style>