voice.vue 20 KB

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