voice.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. <template>
  2. <view class="voice-play-page" v-if="isAuth">
  3. <!-- 由于目前板块较少 先去掉底部弹出筛选 改为平铺展示在tab上 -->
  4. <!-- <view class="top-filter-box">
  5. <image src="@/static/question/select.png" mode="aspectFill" @click="showFilter = true" />
  6. <text @click="showFilter = true">筛选</text>
  7. </view> -->
  8. <view class="top-filter-box">
  9. <van-tabs
  10. id="tabs"
  11. :ellipsis="false"
  12. :active="activeId"
  13. color="#E3B377"
  14. line-width="18px"
  15. @change="onTabsChange"
  16. >
  17. <van-tab
  18. :title="item.SectionName"
  19. :name="item.SectionId"
  20. v-for="item in tabsList"
  21. :key="item.SectionId"
  22. ></van-tab>
  23. </van-tabs>
  24. </view>
  25. <view class="empty-box" v-if="list.length==0&&finished">
  26. <image
  27. :src="globalImgUrls.activityNoAuth"
  28. mode="widthFix"
  29. />
  30. <view>暂无数据</view>
  31. </view>
  32. <view class="list-wrap" :class="showAudioPop?showAudioBigPop?'list-bot3':'list-bot2':'list-bot1'" v-else>
  33. <view class="item" v-for="item in list" :key="item.BroadcastId" @click="handleGoDetail(item)">
  34. <view class="title">{{item.BroadcastName}}</view>
  35. <view class="time">发布时间:{{item.CreateTime|formatTime}}</view>
  36. <view class="flex audio-box" @click.stop="handlePlay(item)">
  37. <image
  38. :src="item.BroadcastId==curVoiceId&&!curAudioPaused?require('@/static/voice/playing.png'):require('@/static/voice/pause.png')"
  39. mode="widthFix"
  40. />
  41. <text>{{item.VoicePlaySeconds|formatVoiceTime}}</text>
  42. </view>
  43. <button class="publish-btn" @click.stop="handleSendMsgItem(item)" v-if="item.CouldSendMsg">
  44. <image class="publish-img" src="@/static/voice/publish.png" mode="widthFix" />
  45. </button>
  46. <button class="del-btn" @click.stop="handleDelItem(item)">
  47. <image class="del-img" src="@/static/voice/del.png" mode="widthFix" v-if="item.IsAuthor" />
  48. </button>
  49. <button
  50. class="share-btn"
  51. open-type="share"
  52. :data-item="item"
  53. @click.stop=""
  54. >
  55. <image class="share-img" src="@/static/share-icon.png" mode="aspectFill"/>
  56. </button>
  57. </view>
  58. </view>
  59. <navigator url="/pages-voice/addVoice">
  60. <view :class="['add-btn',showAudioPop?showAudioBigPop?'add-btn-bot3':'add-btn-bot2':'add-btn-bot1']" v-if="IsVoiceAdmin">新建语音</view>
  61. </navigator>
  62. <!-- 筛选弹窗 -->
  63. <van-popup
  64. :show="showFilter"
  65. position="bottom"
  66. :close-on-click-overlay="true"
  67. @close="showFilter = false"
  68. round
  69. >
  70. <view class="fliter-wrap-list">
  71. <view class="flex top">
  72. <text style="color:#000">全部筛选</text>
  73. <text style="color:#E3B377" @click="showFilter=false">取消</text>
  74. </view>
  75. <van-tree-select
  76. :items="options"
  77. :main-active-index="mainActiveIndex"
  78. :active-id="activeId"
  79. @click-nav="onClickNav"
  80. @click-item="onClickItem"
  81. main-active-class="main-active-class"
  82. content-active-class="content-active-class"
  83. />
  84. </view>
  85. </van-popup>
  86. <!-- 音频悬浮 -->
  87. <view v-if="showPage">
  88. <audioBox v-if="showAudioPop"/>
  89. </view>
  90. <van-dialog id="van-dialog" />
  91. </view>
  92. <noAuth :info="noAuthData" v-else/>
  93. </template>
  94. <script>
  95. import {apiVoiceList,apiVoiceSectionList,apiVoicePlayRecord,apiVoiceDel,apiVoiceSendMsg} from '@/api/voice'
  96. import {apiGetSceneToParams} from '@/api/common'
  97. import noAuth from './components/noAuth.vue'
  98. import audioBox from '@/components/audioBox/audioBox.vue'
  99. const moment=require('@/utils/moment-with-locales.min')
  100. export default {
  101. components:{
  102. noAuth,
  103. audioBox
  104. },
  105. filters:{
  106. formatTime(e){
  107. return moment(e).format('YYYY-MM-DD HH:mm:ss')
  108. },
  109. formatVoiceTime(e){
  110. let m=parseInt(e/60)
  111. let s=parseInt(e%60)
  112. return `${m>9?m:'0'+m}:${s>9?s:'0'+s}`
  113. }
  114. },
  115. computed:{
  116. showAudioPop(){//是否显示音频弹窗
  117. return this.$store.state.audio.show
  118. },
  119. showAudioBigPop(){
  120. return this.$store.state.audio.showBig
  121. },
  122. curVoiceId(){//当前正在播放的音频id
  123. return this.$store.state.audio.voiceId
  124. },
  125. curAudioPaused(){//当前音频是否暂停状态
  126. return this.$store.state.audio.paused
  127. },
  128. },
  129. data() {
  130. return {
  131. list:[],
  132. page:1,
  133. pageSize:20,
  134. finished:false,
  135. voiceId:0,//分享时进入的音频id
  136. IsVoiceAdmin:false,//是否是语音管理员
  137. isAuth:true,
  138. noAuthData:null,
  139. showFilter:false,
  140. options:[],
  141. mainActiveIndex:0,
  142. activeId:0,//选择的板块id
  143. tabsList:[],//顶部滑动筛选数据
  144. showPage:false,
  145. }
  146. },
  147. onLoad(options){
  148. this.init(options)
  149. this.addListenVoiceSuccess()
  150. },
  151. onShow(){
  152. //无权限时刷新列表
  153. if(!this.isAuth){
  154. this.getOptionsList()
  155. }
  156. this.$nextTick(()=>{
  157. this.selectComponent('#tabs').resize();// 解决初始渲染 vant tab 底部条
  158. })
  159. this.showPage=true
  160. },
  161. onHide(){
  162. this.showPage=false
  163. },
  164. onUnload(){
  165. uni.$off('addVoiceSuccess')
  166. },
  167. onShareAppMessage({from,target}) {
  168. console.log(from,target);
  169. let path='/pages/voice/voice'
  170. let title='语音播报'
  171. let imageUrl=''
  172. if(from=='button'){
  173. title=`${target.dataset.item.SectionName}:${target.dataset.item.BroadcastName}`
  174. path=`/pages-voice/voiceDetail?voiceId=${target.dataset.item.BroadcastId}`
  175. imageUrl=target.dataset.item.ImgUrl
  176. }
  177. return {
  178. title:title,
  179. path:path,
  180. imageUrl:imageUrl
  181. }
  182. },
  183. onPullDownRefresh(){
  184. this.voiceId=0
  185. this.page=1
  186. this.list=[]
  187. this.finished=false
  188. this.getOptionsList()
  189. setTimeout(() => {
  190. uni.stopPullDownRefresh()
  191. }, 1500)
  192. },
  193. onReachBottom() {
  194. if(this.finished) return
  195. this.page++
  196. this.getVoiceList()
  197. },
  198. methods: {
  199. handleGoDetail(item){
  200. uni.navigateTo({
  201. url: '/pages-voice/voiceDetail?voiceId='+item.BroadcastId,
  202. });
  203. },
  204. // 监听添加音频成功刷新列表
  205. addListenVoiceSuccess(){
  206. uni.$on('addVoiceSuccess',()=>{
  207. this.voiceId=0
  208. this.page=1
  209. this.list=[]
  210. this.finished=false
  211. this.getVoiceList()
  212. this.getOptionsList()
  213. })
  214. },
  215. async init(options){
  216. if(options.scene){
  217. const res=await apiGetSceneToParams({scene_key:options.scene})
  218. if(res.code===200){
  219. const obj=JSON.parse(res.data)
  220. this.voiceId=obj.voiceId
  221. }
  222. }else{
  223. this.voiceId=options.voiceId||0
  224. }
  225. this.getOptionsList()
  226. },
  227. // 获取音频列表
  228. async getVoiceList(){
  229. const res=await apiVoiceList({
  230. page_index:this.page,
  231. page_size:this.pageSize,
  232. broadcast_id:Number(this.voiceId),
  233. section_id:Number(this.activeId)
  234. })
  235. if(res.code===200){
  236. this.IsVoiceAdmin=res.data.IsVoiceAdmin
  237. let arr=res.data.List||[]
  238. this.list=[...this.list,...arr]
  239. if(arr.length===0){
  240. this.finished=true
  241. }
  242. // 如果有voiceId 则说明是分享进入的 如果没有数据则提示
  243. if(this.voiceId!=0&&arr.length===0){
  244. uni.showToast({
  245. title:'该语音播报不存在',
  246. icon:'none'
  247. })
  248. setTimeout(() => {
  249. this.voiceId=0
  250. this.page=1
  251. this.list=[]
  252. this.finished=false
  253. this.getVoiceList()
  254. this.getOptionsList()
  255. }, 1500);
  256. }
  257. }else if(res.code===403){
  258. //无权限用户
  259. this.isAuth=false
  260. this.noAuthData=res.data
  261. }
  262. },
  263. //获取筛选数据
  264. async getOptionsList(){
  265. const res=await apiVoiceSectionList()
  266. if(res.code!==200) return
  267. const arr=res.data||[]
  268. let temarr=[]
  269. this.options=arr.map(item=>{
  270. let obj={
  271. text:'',
  272. children:[]
  273. }
  274. obj.text=item.VarietyName
  275. obj.children=item.Children.map(_item=>{
  276. temarr.push(_item)
  277. return {
  278. text:_item.SectionName,
  279. id:_item.SectionId
  280. }
  281. })
  282. return obj
  283. })
  284. this.tabsList=temarr||[]
  285. this.activeId=temarr[0].SectionId
  286. this.$nextTick(()=>{
  287. this.selectComponent('#tabs').resize();// 解决初始渲染 vant tab 底部条
  288. })
  289. this.getVoiceList()
  290. },
  291. // 顶部tab切换
  292. onTabsChange(e){
  293. this.activeId=e.detail.name
  294. this.voiceId=0
  295. this.page=1
  296. this.list=[]
  297. this.finished=false
  298. this.getVoiceList()
  299. },
  300. onClickNav({detail}){
  301. console.log(detail);
  302. this.mainActiveIndex=detail.index
  303. },
  304. onClickItem({detail}){
  305. console.log(detail);
  306. if(this.activeId==detail.id){
  307. this.activeId=0
  308. }else{
  309. this.activeId=detail.id
  310. }
  311. this.voiceId=0
  312. this.page=1
  313. this.list=[]
  314. this.finished=false
  315. this.getVoiceList()
  316. this.showFilter=false
  317. },
  318. //推送消息
  319. handleSendMsgItem(item){
  320. this.$dialog.confirm({
  321. title:'',
  322. message: '该操作将推送模板消息和客群,确认推送吗?',
  323. confirmButtonText:'确认'
  324. }).then(()=>{
  325. apiVoiceSendMsg({broadcast_id:item.BroadcastId}).then(res=>{
  326. if(res.code===200){
  327. uni.showToast({
  328. title:"推送成功",
  329. icon:'success'
  330. })
  331. item.CouldSendMsg=false
  332. }
  333. })
  334. }).catch(()=>{})
  335. },
  336. //删除音频
  337. handleDelItem(item){
  338. this.$dialog.confirm({
  339. title:'',
  340. message: '确定要删除该语音播报吗?',
  341. confirmButtonText:'确定'
  342. }).then(()=>{
  343. if(this.temAudio.item&&this.temAudio.item.id==item.BroadcastId){
  344. //删除的音频正好在播放则暂停
  345. this.globalBgMusic.stop()
  346. }
  347. apiVoiceDel({broadcast_id:Number(item.BroadcastId)}).then(res=>{
  348. if(res.code===200){
  349. uni.showToast({
  350. title:'操作成功',
  351. icon:'none'
  352. })
  353. this.page=1
  354. this.list=[]
  355. this.finished=false
  356. this.getVoiceList()
  357. }
  358. })
  359. }).catch(()=>{})
  360. },
  361. //点击音频 播放或者暂停
  362. handlePlay(item){
  363. if(this.$store.state.audio.voiceId==item.BroadcastId){
  364. if(this.globalBgMusic.paused){
  365. this.globalBgMusic.play()
  366. }else{
  367. this.globalBgMusic.pause()
  368. }
  369. }else{
  370. const list=[{url:item.VoiceUrl,time:item.VoicePlaySeconds,title:item.BroadcastName,}]
  371. this.$store.commit('audio/addAudio',{
  372. list:list,
  373. voiceId:item.BroadcastId
  374. })
  375. this.handleVoicePlayRecord(item)
  376. }
  377. },
  378. //上报音频播放记录
  379. async handleVoicePlayRecord(item){
  380. const res=await apiVoicePlayRecord({
  381. broadcast_id:item.BroadcastId
  382. })
  383. if(res.code===200){
  384. console.log('上报音频播放记录');
  385. }
  386. }
  387. },
  388. }
  389. </script>
  390. <style lang="scss">
  391. .top-filter-box{
  392. .van-tab{
  393. font-size: 28rpx;
  394. color: #777;
  395. }
  396. .van-tab--active{
  397. font-weight: bold;
  398. font-size: 32rpx;
  399. color: #333333;
  400. }
  401. }
  402. .voice-play-page{
  403. .fliter-wrap-list{
  404. background-color: #fff;
  405. padding-top: 53rpx;
  406. padding-bottom: 100rpx;
  407. .top{
  408. font-size: 32rpx;
  409. justify-content: space-between;
  410. margin-bottom: 40rpx;
  411. padding: 0 34rpx;
  412. }
  413. .van-sidebar{
  414. flex-shrink: 0;
  415. }
  416. .van-tree-select__content{
  417. overflow-x: hidden;
  418. }
  419. .main-active-class{
  420. border-color: #E3B377;
  421. }
  422. .content-active-class{
  423. color: #E3B377;
  424. }
  425. }
  426. }
  427. </style>
  428. <style lang="scss" scoped>
  429. .voice-play-page{
  430. .top-filter-box{
  431. background-color: white;
  432. box-shadow: 0px 4rpx 4rpx 0px rgba(198,198,198,0.2500);
  433. position: sticky;
  434. top: 0;
  435. left: 0;
  436. z-index: 99;
  437. // image{
  438. // width: 34rpx;
  439. // height: 34rpx;
  440. // }
  441. // color: #E3B377;
  442. // font-size: 28rpx;
  443. }
  444. }
  445. .empty-box{
  446. text-align: center;
  447. font-size: 32rpx;
  448. color: #999;
  449. padding-top: 150rpx;
  450. image{
  451. width: 80vw;
  452. margin-bottom: 57rpx;
  453. }
  454. }
  455. .list-wrap{
  456. padding: 0 34rpx 34rpx 34rpx;
  457. .item{
  458. border-bottom: 1px solid #CDCDCD;
  459. padding: 30rpx 0;
  460. position: relative;
  461. .publish-btn{
  462. position: absolute;
  463. right: 192rpx;
  464. bottom: 30rpx;
  465. width: 36rpx;
  466. height: 36rpx;
  467. background-color: transparent;
  468. line-height: 1;
  469. padding: 0;
  470. &::after{
  471. border: none;
  472. }
  473. }
  474. .publish-img{
  475. width: 34rpx;
  476. height: 34rpx;
  477. }
  478. .del-btn{
  479. position: absolute;
  480. right: 96rpx;
  481. bottom: 30rpx;
  482. width: 36rpx;
  483. height: 36rpx;
  484. background-color: transparent;
  485. line-height: 1;
  486. padding: 0;
  487. &::after{
  488. border: none;
  489. }
  490. }
  491. .del-img{
  492. width: 34rpx;
  493. height: 34rpx;
  494. }
  495. .share-btn{
  496. position: absolute;
  497. bottom: 30rpx;
  498. right: 0rpx;
  499. background-color: transparent;
  500. width: 36rpx;
  501. height: 36rpx;
  502. line-height: 1;
  503. padding: 0;
  504. &::after{
  505. border: none;
  506. }
  507. }
  508. .share-img{
  509. width: 32.5rpx;
  510. height: 32rpx;
  511. }
  512. .title{
  513. font-size: 32rpx;
  514. }
  515. .time{
  516. font-size: 28rpx;
  517. color: #666;
  518. margin-top: 20rpx;
  519. margin-bottom: 30rpx;
  520. }
  521. .audio-box{
  522. width: 185rpx;
  523. height: 56rpx;
  524. align-items: center;
  525. justify-content: center;
  526. border-radius: 28rpx;
  527. background-color: #F4E1C9;
  528. color: #E3B377;
  529. image{
  530. width: 23rpx;
  531. height: 28rpx;
  532. margin-right: 20rpx;
  533. }
  534. }
  535. }
  536. }
  537. .list-bot1{
  538. padding-bottom: 200rpx;
  539. }
  540. .list-bot2{
  541. padding-bottom: 340rpx;
  542. }
  543. .list-bot3{
  544. padding-bottom: 440rpx;
  545. }
  546. .add-btn{
  547. position: fixed;
  548. width: 514rpx;
  549. height: 80rpx;
  550. text-align: center;
  551. line-height: 80rpx;
  552. color: #E3B377;
  553. font-size: 32rpx;
  554. left: 50%;
  555. bottom: calc(150rpx + constant(safe-area-inset-bottom));
  556. bottom: calc(150rpx + env(safe-area-inset-bottom));
  557. transform: translateX(-50%);
  558. background: #333333;
  559. box-shadow: 0px 4rpx 20rpx rgba(160, 126, 84, 0.25);
  560. border-radius: 40rpx;
  561. }
  562. .add-btn-bot1{
  563. bottom: calc(120rpx + constant(safe-area-inset-bottom));
  564. bottom: calc(120rpx + env(safe-area-inset-bottom));
  565. }
  566. .add-btn-bot2{
  567. bottom: calc(260rpx + constant(safe-area-inset-bottom));
  568. bottom: calc(260rpx + env(safe-area-inset-bottom));
  569. }
  570. .add-btn-bot3{
  571. bottom: calc(355rpx + constant(safe-area-inset-bottom));
  572. bottom: calc(355rpx + env(safe-area-inset-bottom));
  573. }
  574. </style>