voice.vue 19 KB

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