Browse Source

将moment换成day

jwyu 2 years ago
parent
commit
172b6ab16a
53 changed files with 3555 additions and 382 deletions
  1. 14 1
      App.vue
  2. 2 0
      README.md
  3. 18 0
      api/activity.js
  4. 7 0
      api/common.js
  5. 9 0
      api/report.js
  6. 24 0
      api/roadShow.js
  7. 1 1
      api/user.js
  8. 73 0
      api/voice.js
  9. 90 58
      components/audioBox/audioBox.vue
  10. 141 0
      components/dragButton/dragButton.vue
  11. 39 13
      components/questionComment/questionComment.vue
  12. 29 1
      mixin/index.js
  13. 27 0
      mixin/questionMixin.js
  14. 44 7
      pages-activity/detail.vue
  15. 2 1
      pages-activity/noAuthority.vue
  16. 1 1
      pages-applyPermission/applyPermission.vue
  17. 65 20
      pages-question/answerDetail.vue
  18. 28 3
      pages-question/answerList.vue
  19. 2 1
      pages-report/chapterDetail.vue
  20. 194 0
      pages-report/previewImage.vue
  21. BIN
      pages-report/static/close.png
  22. 99 0
      pages-roadShow/video/components/noAuth.vue
  23. 461 0
      pages-roadShow/video/list.vue
  24. 267 0
      pages-roadShow/video/search.vue
  25. 55 12
      pages-sandTable/sandTable.vue
  26. 335 51
      pages-voice/addVoice.vue
  27. 450 0
      pages-voice/myVoice.vue
  28. BIN
      pages-voice/static/camera.png
  29. BIN
      pages-voice/static/clock-icon.png
  30. BIN
      pages-voice/static/publish.png
  31. 317 0
      pages-voice/voiceDetail.vue
  32. 43 3
      pages.json
  33. 49 6
      pages/activity/activity.vue
  34. 2 1
      pages/chart/component/noAuth.vue
  35. 3 1
      pages/pc.vue
  36. 1 1
      pages/pricedriven/pricedriven.vue
  37. 137 47
      pages/question/question.vue
  38. 43 40
      pages/report/report.vue
  39. 13 0
      pages/user/user.vue
  40. 99 0
      pages/video/components/noAuth.vue
  41. 40 6
      pages/video/videoList.vue
  42. 2 1
      pages/voice/components/noAuth.vue
  43. 192 95
      pages/voice/voice.vue
  44. BIN
      static/sandTable/sandBox-share-icon.png
  45. BIN
      static/toquestion-icon.png
  46. BIN
      static/voice/mine-voice-icon.png
  47. BIN
      static/voice/publish.png
  48. 3 1
      store/index.js
  49. 58 0
      store/modules/audio.js
  50. 23 0
      style/common.scss
  51. 1 1
      utils/config.js
  52. 9 8
      utils/request.js
  53. 43 1
      utils/upload.js

+ 14 - 1
App.vue

@@ -10,7 +10,7 @@
 				success: function (res) {
 					console.log('宽度:',res.windowWidth);
 					console.log('设备:',res.platform);
-					if (res.windowWidth > 600||['windows','mac'].includes(res.platform)) {
+					if (res.windowWidth > 750||['windows','mac'].includes(res.platform)) {
 						const params=options.query//此处的query就是在pc分享钩子函数中拼接的参数
 						let paramsStr=`xcxPath=${decodeURIComponent(options.path)}`
 						for(const key in params){
@@ -30,6 +30,19 @@
 			wx.setInnerAudioOption({
 				obeyMuteSwitch:false
 			})
+			//设置在小程序内屏幕常亮(防止录音等操作自动息屏会停止录音)
+			wx.setKeepScreenOn({
+				keepScreenOn: true,
+				fail(res){
+					console.log('设置屏幕常亮失败:',res);
+				}
+			})
+			if(ENV.envVersion!=='release'){
+				// 打开调试
+				wx.setEnableDebug({
+					enableDebug: true
+				})
+			}
 		},
 		onShow: function(options) {
 			console.log('App Show')

+ 2 - 0
README.md

@@ -7,5 +7,7 @@
   5. 所有接口请用 api 开头,如:'apiUserInfo'
   6. 不可将会动态显示的tabbar项放在pages.json配置为启动页,如需改变tabbar 请调用this.$store.dispatch('getTabBar')
   7. tabbar的默认数据在config中
+  8. 如果是从海报扫码进入的(获取到小程序带来的参数为scene),均要调用apiGetSceneToParams接口获取实际参数
+  9. master_V2 为备用小程序分支,如要发布请在uniapp中发布时看清楚appid:wx9a2a9b49a00513a0 是否正确;并且在所有请求的请求头中加了参数CopyYb=true字段
 
 	

+ 18 - 0
api/activity.js

@@ -61,3 +61,21 @@ export const apiActivityCancelRegister = (params) => {
 export const apiActivityAudios=params=>{
     return httpGet('/activity/getActivityVoices',params)
 }
+
+/**
+ * 新增活动音频播放记录
+ * @param primary_id 音频ID
+ * @param extend_id 活动ID
+ * @param source 来源:1-小程序;2-小程序PC端;3-公众号;4-Web PC端
+ * @param from_page 来源页面:eg:活动详情
+ */
+export const apiActivityAudioPlayRecordAdd=params=>{
+    return httpPost('/activity/voice_log/add',{...params,source:1})
+}
+
+/**
+ * 新增活动音频播放记录
+ */
+export const apiActivityAudioPlayRecordUpate=params=>{
+    return httpPost('/activity/voice_log/update',params)
+}

+ 7 - 0
api/common.js

@@ -60,4 +60,11 @@ export const apiGetSceneToParams=params=>{
  */
 export const apiGetTagTree = params=>{
     return httpGet('/public/get_variety_tag_tree',params)
+}
+
+/**
+ * 获取用户已绑定权限
+ */
+export const apiUserBindPermission=()=>{
+    return httpGet('/company/permission/bind',{})
 }

+ 9 - 0
api/report.js

@@ -98,4 +98,13 @@ export const apiSpecialColumnReportList=params=>{
  */
 export const apiChapterTickerValue=params=>{
     return httpGet('/report/chapter/ticker',params)
+}
+
+/**
+ * 报告对应的ppt图片
+ * @param report_id 报告id
+ * @param report_chapter_id 报告章节id
+ */
+export const apiReportPPtImgs=params=>{
+    return httpGet('/report/ppt_img',params)
 }

+ 24 - 0
api/roadShow.js

@@ -0,0 +1,24 @@
+// 路演视频模块
+
+import { httpGet, httpPost } from "@/utils/request.js";
+
+/**
+ * 视频列表
+ * @param page_index
+ * @param page_size
+ * @param keywords
+ * @param video_id
+ * @param chart_permission_id
+ */
+export const apiRoadShowVideoList=params=>{
+    return httpGet('/road/video/list',params)
+}
+
+/**
+ * 视频播放埋点
+ * @param video_id
+ * @param source_agent 来源平台:1:小程序、2:小程序(pc)、3:公众号、4:官网web(pc)
+ */
+export const apiRoadShowVideoPlayLog=params=>{
+    return httpPost('/road/video/play_log',{...params,source_agent:1})
+}

+ 1 - 1
api/user.js

@@ -48,7 +48,7 @@ export const apiUserLogin=params=>{
  * @param company_name 公司名
  * @param permission 选择的权限
  * @param real_name 姓名
- * @param source 来源:我的1、活动2、图库3、研报4、问答社区5、价格驱动6、沙盘推演7、语音播报8
+ * @param source 来源:我的1、活动2、图库3、研报4、问答社区5、价格驱动6、沙盘推演7、语音播报8、视频社区9、线上路演10
  * @param source_agent 来源平台:1:小程序、2:小程序(pc)、3:公众号、4:官网web(pc)
  * @param from_page 来源页面: '活动列表'、'活动详情'等
  */

+ 73 - 0
api/voice.js

@@ -8,6 +8,8 @@ import { httpGet, httpPost } from "@/utils/request.js";
  * @param page_size
  * @param broadcast_id 语音id
  * @param section_id 板块id
+ * @param author_id 作者ID(我的语音播报列表)
+ * @param mine_status 语音播报状态:0-未发布 1-已发布 2-全部(我的语音播报列表)
  */
 export const apiVoiceList=params=>{
     return httpPost('/voice/broadcast/list',params)
@@ -35,4 +37,75 @@ export const apiVoiceDel=params=>{
  */
 export const apiVoicePlayRecord=params=>{
     return httpPost('/voice/broadcast/statistics/add',{...params,source:1})
+}
+
+/**
+ * 语音详情
+ * @param broadcast_id
+ */
+export const apiVoiceDetail=params=>{
+    return httpGet('/voice/broadcast/detail',params)
+}
+
+/**
+ * 推送客群、模板消息
+ * @param broadcast_id
+ */
+export const apiVoiceSendMsg=params=>{
+    return httpPost('/voice/broadcast/msg_send',params)
+}
+
+/**
+ * 新增语音
+ * @param broadcast_name 语音标题
+ * @param section_id 板块ID
+ * @param section_name 板块名称
+ * @param variety_id 品种ID
+ * @param variety_name 品种名称
+ * @param author_id 作者ID
+ * @param author 作者名称
+ * @param imgs 图片:英文逗号拼接
+ * @param voice_seconds 音频时长
+ * @param voice_size 音频大小
+ * @param voice_url 音频地址
+ */
+export const apiVoiceAdd=params=>{
+    return httpPost('/voice/broadcast/add',params)
+}
+
+/**
+ * 编辑语音
+ * @param broadcast_id 语音播报ID
+ * @param broadcast_name 语音标题
+ * @param section_id 板块ID
+ * @param section_name 板块名称
+ * @param variety_id 品种ID
+ * @param variety_name 品种名称
+ * @param author_id 作者ID
+ * @param author 作者名称
+ * @param imgs 图片:英文逗号拼接
+ * @param voice_seconds 音频时长
+ * @param voice_size 音频大小
+ * @param voice_url 音频地址
+ */
+export const apiVoiceEdit=params=>{
+    return httpPost('/voice/broadcast/edit',params)
+}
+
+/**
+ * 发布语音
+ * @param broadcast_id 语音播报ID
+ * @param publish_type 发布类型:1-发布 2-定时发布
+ * @param pre_publish_time 预发布时间(类型为定时发布时必填)
+ */
+export const apiVoicePublish=params=>{
+    return httpPost('/voice/broadcast/publish',params)
+}
+
+/**
+ * 我的语音数量统计
+ * @param author_id
+ */
+export const apiMyVoiceCount=params=>{
+    return httpGet('/voice/broadcast/list_count',params)
 }

+ 90 - 58
components/voiceBox/voiceBox.vue → components/audioBox/audioBox.vue

@@ -1,17 +1,19 @@
 <template>
     <view class="global-voice-box">
-        <view class="flex small-box" v-if="!showBig" @click.prevent="showBig=true">
-            <view style="flex:1;overflow: hidden;">
-                <view class="van-ellipsis">{{voiceData.title}}</view>
-                <view class="time" style="font-size:24rpx;color:#666">时长 {{voiceData.duration|formatVoiceTime}}</view>
+        <view v-if="!showBig">
+            <view class="flex small-box"  @click.prevent="showBig=true">
+                <view style="flex:1;overflow: hidden;">
+                    <view class="van-ellipsis">{{title}}</view>
+                    <view class="time" style="font-size:24rpx;color:#666">时长 {{audioTime|formatVoiceTime}}</view>
+                </view>
+                <image 
+                    class="pause-img"  
+                    :src="play?require('@/static/audio-doing.png'):require('@/static/audio-pause-3.png')" 
+                    mode="aspectFill"
+                    @click.stop="handleChangePlayStatus"
+                />
+                <van-icon @click.stop="handleClosePopAudio" name="cross" size="18" color="#BBC3C9"/>
             </view>
-            <image 
-                class="pause-img"  
-                :src="play?require('@/static/audio-doing.png'):require('@/static/audio-pause-3.png')" 
-                mode="aspectFill"
-                @click.stop="handleChangePlayStatus"
-            />
-            <van-icon @click.stop="handleClosePopAudio" name="cross" size="18" color="#BBC3C9"/>
             <van-progress 
                 color="#D4AC78" 
                 track-color="#fff"
@@ -21,10 +23,11 @@
                 :percentage="percentage" 
             />
         </view>
+          
         <view class="big-box" v-else>
             <view class="flex top" style="overflow: hidden;">
                 <van-icon name="arrow-down" size="18" color="#BBC3C9" @click="showBig=false" />
-                <view class="van-ellipsis" style="flex:1;margin:0 10rpx;text-align:center">{{voiceData.title}}</view>
+                <view class="van-ellipsis" style="flex:1;margin:0 10rpx;text-align:center">{{title}}</view>
                 <van-icon @click.stop="handleClosePopAudio" name="cross" size="18" color="#BBC3C9"/>
             </view>
             <view class="flex center">
@@ -37,13 +40,13 @@
                 <text class="time">{{curTime|formatVoiceTime}}</text>
                 <slider
                     activeColor="#e3b377"
-                    :max="voiceData.duration" 
+                    :max="audioTime" 
                     :value="curTime" 
                     @change="handleAudioSliderChange($event)"
                     block-size="16"
                     class="slider"
                 />
-                <text class="time">{{voiceData.duration|formatVoiceTime}}</text>
+                <text class="time">{{audioTime|formatVoiceTime}}</text>
             </view>
         </view>
     </view>
@@ -59,63 +62,73 @@ export default {
             return `${m>9?m:'0'+m}:${s>9?s:'0'+s}`
         }
     },
-    props:{
-        voiceData:null,//{title:音频标题,duration:音频时长,url:音频地址,id:音频唯一id}
-    },
+
     computed: {
+        audioData(){
+            return this.$store.state.audio
+        },
+        //用户监听音频是否变化了 是否需要初始化
+        audioInit(){
+            return {
+                reportId:this.$store.state.audio.reportId,
+                voiceId:this.$store.state.audio.voiceId,
+                questionId:this.$store.state.audio.questionId
+            }
+        },
+
         percentage(){
-            return parseInt((this.curTime/this.voiceData.duration)*100)
+            return parseInt((this.curTime/this.audioTime)*100)
         },
+
         reportAudioShow(){
             return this.$store.state.report.audioData.show
         }
     },
     watch:{
-        'voiceData':{
+        audioInit:{
             handler(nval){
                 console.log(nval);
                 this.init()
             },
-            deep:true,
             immediate:true
         },
-        play(nval){
-            this.$emit('stateChange',nval)
-        },
-        showBig(nval){
-            this.$emit('popChange',this.showBig)
-        },
-        reportAudioShow(nval){
-            if(this.$store.state.report.audioData.show){
-                this.$emit('closeVoice')
-            }
+        showBig:{
+            handler(nval){
+                this.$store.commit('audio/showBig',nval)
+            },
+            immediate:true
         }
     },
     data() {
         return {
             showBig:false,
             curTime:0,
+            audioTime:0,//当前音频总时长
+            title:'',//当前音频标题
             play:false
         }
     },
-    // mounted() {
-    //     this.listenAudio()
-    // },
     methods: {
         init(){
+            console.log('audioBox init');
+            let delyTime=0
             if(this.$store.state.report.audioData.show){
                 this.globalBgMusic.stop()
-                setTimeout(() => {
-                    this.globalBgMusic.src=this.voiceData.url 
-                    this.globalBgMusic.title=this.voiceData.title
-                    this.listenAudio()
-                }, 300);
-            }else{
-                this.globalBgMusic.src=this.voiceData.url 
-                this.globalBgMusic.title=this.voiceData.title
-                this.listenAudio()
+                delyTime=300
             }
-            
+            console.log('audioBox init',delyTime);
+            const curAudio=this.$store.state.audio.list[this.$store.state.audio.index]
+            setTimeout(() => {
+                if(this.globalBgMusic.src!=curAudio.url){
+                    this.globalBgMusic.src=curAudio.url 
+                    this.globalBgMusic.title=curAudio.title
+                }
+                this.audioTime=curAudio.time
+                this.title=curAudio.title
+                this.curTime=parseInt(this.globalBgMusic.currentTime)
+                this.play=!this.globalBgMusic.paused
+                this.listenAudio()
+            }, delyTime);
         },
 
         //音频播放事件
@@ -124,28 +137,31 @@ export default {
             this.globalBgMusic.onPlay(()=>{
                 console.log('开始播放');
                 this.play=true
+                this.$store.commit('audio/updateAudioPause',false)
             })
             this.globalBgMusic.onPause(()=>{
                 console.log('音频暂停');
                 this.play=false
+                this.$store.commit('audio/updateAudioPause',true)
             })
             this.globalBgMusic.onStop(()=>{
                 console.log('音频停止');
-                this.play=false
-                this.showBig=false
-                this.handleClosePopAudio()
+                this.$store.commit('audio/removeAudio')
             })
             this.globalBgMusic.onEnded(()=>{
                 console.log('音频onEnded');
-                this.play=false
-                this.showBig=false
-                this.handleClosePopAudio()
+
+                const index=this.$store.state.audio.index 
+                if(index==this.$store.state.audio.list.length-1){
+                    this.$store.commit('audio/removeAudio')
+                }else{
+                    this.handleAudioChange('next')
+                }
+                
             })
             this.globalBgMusic.onError((e)=>{
                 console.log('音频onError',e);
-                this.play=false
-                this.showBig=false
-                this.handleClosePopAudio()
+                this.$store.commit('audio/removeAudio')
                 uni.showToast({
                     title: '音频播放错误',
                     icon: 'none'
@@ -154,12 +170,31 @@ export default {
             this.globalBgMusic.onTimeUpdate(()=>{
                 // console.log('时间更新');
                 this.curTime=parseInt(this.globalBgMusic.currentTime)
+                this.$store.commit('audio/updateAudioTime',this.curTime)
             })
         },
 
+        //关闭弹窗停止播放的音频
         handleClosePopAudio(){
             this.globalBgMusic.stop()
-            this.$emit('closeVoice')
+        },
+
+        //音频切换(暂时没有用到因为都是一个音频 如果后期有多个 直接在页面dom调用此方法即可)
+        handleAudioChange(type){
+            let temIndex=this.$store.state.report.audioData.index
+            if(type=='before'){
+                if(temIndex>0){
+                    let index=temIndex-1
+                    this.$store.commit('audio/updateAudioIndex', index)
+                    this.init()
+                }
+            }else{
+                if(temIndex<this.$store.state.report.audioData.list.length-1){
+                    let index=temIndex+1
+                    this.$store.commit('audio/updateAudioIndex', index)
+                    this.init()
+                }
+            }
         },
 
         //拖动进度条
@@ -170,7 +205,7 @@ export default {
 
         //音频点击暂停播放
         handleChangePlayStatus(){
-            if(this.play){
+            if(!this.globalBgMusic.paused){
                 this.globalBgMusic.pause()
             }else{
                 this.globalBgMusic.play()
@@ -183,10 +218,7 @@ export default {
 
 <style>
 .bot-progress{
-    position: absolute;
-    bottom: 0;
-    left: 0;
-    right: 0;
+    width: 100%;
 }
 </style>
 

+ 141 - 0
components/dragButton/dragButton.vue

@@ -0,0 +1,141 @@
+<template>
+	<view>
+		<view
+			id="_drag_button"
+			class="drag"
+			:style="'left: ' + left + 'px; top:' + top + 'px;'"
+			@touchstart="touchstart"
+			@touchmove.stop.prevent="touchmove"
+			@touchend="touchend"
+			@click.stop.prevent="click"
+			:class="{transition: isDock && !isMove }"
+		>
+			<slot></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'drag-button',
+		props: {
+			isDock:{
+				type: Boolean,
+				default: true
+			},
+			existTabBar:{
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				top:1000,
+				left:1000,
+				width: 0,
+				height: 0,
+				offsetWidth: 0,
+				offsetHeight: 0,
+				windowWidth: 0,
+				windowHeight: 0,
+				isMove: false,
+				edge: 10,
+			}
+		},
+		mounted() {
+			const sys = uni.getSystemInfoSync();
+			
+			this.windowWidth = sys.windowWidth;
+			this.windowHeight = sys.windowHeight;
+			
+
+			this.existTabBar && (this.windowHeight -= 80);
+
+			if (sys.windowTop) {
+				this.windowHeight += sys.windowTop;
+			}
+			// console.log(sys)
+			
+			const query = uni.createSelectorQuery().in(this);
+			query.select('#_drag_button').boundingClientRect(data => {
+				this.width = data.width;
+				this.height = data.height;
+				this.offsetWidth = data.width / 2;
+				this.offsetHeight = data.height / 2;
+				this.left = this.windowWidth - this.width+4;
+				this.top = this.windowHeight - this.height - this.edge;
+			}).exec();
+		},
+		methods: {
+			click() {
+				this.$emit('btnClick');
+			},
+			touchstart(e) {
+				this.$emit('btnTouchstart');
+			},
+			touchmove(e) {
+				// 单指触摸
+				if (e.touches.length !== 1) {
+					return false;
+				}
+				
+				this.isMove = true;
+				
+				this.left = e.touches[0].clientX - this.offsetWidth;
+				
+				let clientY = e.touches[0].clientY - this.offsetHeight;
+				// #ifdef H5
+					clientY += this.height;
+				// #endif
+				let edgeBottom = this.windowHeight - this.height - this.edge;
+
+				// 上下触及边界
+				if (clientY < this.edge) {
+					this.top = this.edge;
+				} else if (clientY > edgeBottom) {
+					this.top = edgeBottom;
+				} else {
+					this.top = clientY
+				}
+			},
+			touchend(e) {
+				if (this.isDock) {
+					let edgeRigth = this.windowWidth - this.width;
+					
+					if (this.left < this.windowWidth / 2 - this.offsetWidth) {
+						this.left = -4;
+					} else {
+						this.left = edgeRigth+4;
+					}
+					
+				}
+				
+				this.isMove = false;
+				
+				this.$emit('btnTouchend');
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	.drag {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		// background-color: rgba(0, 0, 0, 0.5);
+		// box-shadow: 0 0 6upx rgba(0, 0, 0, 0.4);
+		// color: $uni-text-color-inverse;
+		min-width: 80upx;
+		min-height: 80upx;
+		// border-radius: 50%;
+		// font-size: $uni-font-size-sm;
+		position: fixed;
+		z-index: 99;
+		
+		&.transition {
+			transition: left .3s ease,top .3s ease;
+		}
+	}
+	
+</style>

+ 39 - 13
components/questionComment/questionComment.vue

@@ -3,28 +3,30 @@
 <template>
 	<view class="question-comment-box">
 		<!-- 第一条评论 -->
-		<view class="first-comment" v-if="data.comment">
+		<view class="first-comment" v-if="data.comment_list.length>0">
 			<!-- <img :src="comment_img" alt="" class="icon"> -->
 			<view class="comment">
-				{{data.comment_user_name}}:
-				{{data.comment}}
+				<template v-for="(item,index) in data.comment_list">
+					<image class="avatar" :src="item.qa_avatar_url" mode="aspectFill" lazy-load="false" :key="index"/>
+					<text :key="index">{{item.comment}}</text>
+				</template>
 			</view>
 		</view>	
 			
 		<!--  -->
 		<view class="question-comment-wrapper">
-			<view class="commetn-item-wrap" @click="setLikeHandle(2)">
+			<view class="commetn-item-wrap" @click.stop="setLikeHandle(1)">
+				<img :src="data.op_type===1?like_act_img:like_img" alt="" class="icon">
+				<text v-if="data.like_total">{{data.like_total}}</text>
+			</view>
+			<view class="commetn-item-wrap" @click.stop="setLikeHandle(2)" style="justify-content:center;">
 				<img :src="data.op_type===2?tease_act_img:tease_img" alt="" class="icon">
 				<text v-if="data.tease_total">{{data.tease_total}}</text>
 			</view>
-			<view class="commetn-item-wrap" @click="openCommentHandle" style="justify-content:center;">
+			<view class="commetn-item-wrap" @click.stop="openCommentHandle" style="justify-content:flex-end;">
 				<img :src="comment_img" alt="" class="icon"> 
 				<text v-if="data.comment_total">{{data.comment_total}}</text>
 			</view>
-			<view class="commetn-item-wrap" @click="setLikeHandle(1)" style="justify-content:flex-end;">
-				<img :src="data.op_type===1?like_act_img:like_img" alt="" class="icon">
-				<text v-if="data.like_total">{{data.like_total}}</text>
-			</view>
 		</view>
 		
 	</view>
@@ -84,11 +86,35 @@
    padding: 10rpx;
 	.comment {
 		color: #666;
-		display: flex;
-		flex-wrap: wrap;
-		.commenter {
-			color: #000;
+		font-size: 28rpx;
+		.avatar{
+			width: 56rpx;
+			height: 56rpx;
+			border-radius: 50%;
+			margin-right: 14rpx;
+			vertical-align: middle;
+		}
+		text{
+			vertical-align: middle;
+			line-height: 56rpx;
+			margin-right: 20rpx;
 		}
+		// display: flex;
+		// flex-wrap: wrap;
+		// .commenter {
+		// 	color: #000;
+		// }
+		// .avatar{
+		// 	width: 56rpx;
+		// 	height: 56rpx;
+		// 	margin-right: 20rpx;
+		// 	flex-shrink: 0;
+		// 	border-radius: 50%;
+		// }
+		// view{
+		// 	flex: 1;
+		// 	position: relative;
+		// }
 	}
 }
 .question-comment-wrapper {

+ 29 - 1
mixin/index.js

@@ -70,7 +70,7 @@ module.exports = {
         console.log('宽度:',res.windowWidth);
         console.log('设备:',res.platform);
         if(page.route=='pages/login'||page.route=='pages/pc') return
-        if (res.windowWidth > 600||['windows','mac'].includes(res.platform)) {
+        if (res.windowWidth > 750||['windows','mac'].includes(res.platform)) {
           const params=options
           let paramsStr=`xcxPath=${decodeURIComponent(page.route)}`
           for(const key in params){
@@ -133,5 +133,33 @@ module.exports = {
       }
     },
 
+    //全局检测用户是否绑定过方法(用户点击前的判断或者其他时候的判断)
+    checkUserIsBind(){
+      return new Promise((resovle,reject)=>{
+        if(store.state.user.userInfo.is_bind===0){
+          wx.showModal({
+            title: '温馨提示',
+            content: '为了优化您的用户体验,\n 请登录后查看更多信息!',
+            confirmText:'去登录',
+            cancelColor:'#666',
+            confirmColor:'#E6B77D',
+            success: function (res) {
+              if (res.confirm) {
+                console.log('用户点击确定');
+                uni.reLaunch({
+                  url:'/pages/login'
+                })
+              } else if (res.cancel) {
+                console.log('用户点击取消');
+              }
+            }
+          });
+          reject()
+        }else{
+          resovle('不用登录')
+        }
+      })
+    }
+
   },
 };

+ 27 - 0
mixin/questionMixin.js

@@ -352,6 +352,7 @@ export default {
         },
         //申请权限
         async handleGoApply(){
+            await this.checkUserIsBind()
             const {customer_info} = this.noAuthInfo
             if (customer_info.has_apply) { //已经申请过
                 uni.showToast({
@@ -382,5 +383,31 @@ export default {
                 }
             }
         },
+
+        //点击播放音频
+		handlePlayAudioByBg(item){
+			const audioItem=item.audio_list[0]
+			if(this.$store.state.audio.questionId==item.community_question_id){
+                if(this.globalBgMusic.paused){
+                    this.globalBgMusic.play()
+                }else{
+                    this.globalBgMusic.pause()
+                }
+            }else{
+                const list=[{url:audioItem.audio_url,time:audioItem.audio_play_seconds,title:item.question_content}]
+                this.$store.commit('audio/addAudio',{
+                    list:list,
+                    questionId:item.community_question_id
+                })
+                apiCountAudioClick({
+                    community_question_audio_id:audioItem.community_question_audio_id,
+                    source_agent:1
+                }).then((res)=>{
+                    if(res.code===200){
+                      console.log('音频id为'+audioItem.community_question_audio_id+'点击次数+1')  
+                    }
+                })
+            }
+		}
     }
 }

+ 44 - 7
pages-activity/detail.vue

@@ -183,7 +183,9 @@ import {
     apiActivityAddRemind,
     apiActivityCancelRemind,
     apiActivityRegister,
-    apiActivityCancelRegister
+    apiActivityCancelRegister,
+    apiActivityAudioPlayRecordAdd,
+    apiActivityAudioPlayRecordUpate
 } from "@/api/activity";
 import {apiGetSceneToParams} from '@/api/common'
 import {baseApiUrl} from '@/utils/config.js'
@@ -233,6 +235,7 @@ export default {
             activeAudioTime:0,//选择的音频数据时长
             audioCurrentTime:0,//音频正常播放的时间
             audioPlayStatus:false,//音频是否正在播放
+            recordId:0,//新增音频播放记录成功的id
 
             pupData: {
                 show: false,
@@ -266,6 +269,7 @@ export default {
                 audioCurrentTime:this.audioCurrentTime,//音频播放实时时间
                 audioTime:this.activeAudioTime,//当前音频时间
                 audioCurrentUrl:this.activeAudioUrl,//当前音频地址
+                recordId:this.recordId
             }
             uni.setStorageSync('audioMsg', JSON.stringify(obj))
         }
@@ -317,14 +321,42 @@ export default {
                 //     this.globalBgMusic.play()
                 // }
             }else{
+                let obj=uni.getStorageSync('audioMsg')
+                if(obj&&JSON.parse(obj).audioCurrentUrl){
+                    this.handleAudioPlayRecordUpdate(this.globalBgMusic.currentTime)
+                }
                 this.handlePlayAudio(item)
             }
         },
 
+        //新增音频播放统计
+        async handleAudioPlayRecordAdd(primary_id,extend_id){
+            const res=await apiActivityAudioPlayRecordAdd({
+                primary_id:Number(primary_id),
+                extend_id:Number(extend_id),
+                from_page:'活动详情'
+            })
+            if(res.code===200){
+                console.log('新增音频播放记录成功');
+                this.recordId=res.data.id
+            }
+        },
+
+        //更新音频播放记录
+        async handleAudioPlayRecordUpdate(time){
+            console.log(time);
+            const res=await apiActivityAudioPlayRecordUpate({
+                id:Number(this.recordId),
+                stop_seconds:time?parseInt(time):Number(this.audioCurrentTime)
+            })
+            if(res.code===200){
+                console.log('更新音频播放记录成功');
+            }
+        },
+
         // 播放音频
         handlePlayAudio(e){
-            console.log(e);
-            
+            this.handleAudioPlayRecordAdd(e.activity_voice_id,e.activityId)
             this.globalBgMusic.title=e.voiceName
             this.globalBgMusic.src=e.voiceUrl
 
@@ -350,13 +382,15 @@ export default {
             })
             this.globalBgMusic.onStop(()=>{
                 console.log('onStop');
-               this.audioPlayStatus=false
-               this.activeAudioUrl=''
-               this.activeAudioTime=0
-               this.audioCurrentTime=0
+                this.handleAudioPlayRecordUpdate()
+                this.audioPlayStatus=false
+                this.activeAudioUrl=''
+                this.activeAudioTime=0
+                this.audioCurrentTime=0
             })
             this.globalBgMusic.onEnded(()=>{
                 console.log('onEnded');
+                this.handleAudioPlayRecordUpdate()
                 this.audioPlayStatus=false
                 this.handleAudioBtn('next','auto')
             })
@@ -391,6 +425,7 @@ export default {
 
             if(type==='before'){
                 if(!this.isFirstAudio){
+                    this.handleAudioPlayRecordUpdate()
                     this.audioList.forEach((_item,index)=>{
                         if(_item.voiceUrl==this.activeAudioUrl){
                             this.handlePlayAudio(this.audioList[index-1])
@@ -401,6 +436,7 @@ export default {
 
             if(type==='next'){
                 if(!this.isLastAudio){
+                    this.handleAudioPlayRecordUpdate()
                     this.audioList.forEach((_item,index)=>{
                         if(_item.voiceUrl==this.activeAudioUrl){
                             this.handlePlayAudio(this.audioList[index+1])
@@ -441,6 +477,7 @@ export default {
                         this.audioPlayStatus=JSON.parse(obj).play
                         this.handleAudioFun()
                     }
+                    this.recordId=JSON.parse(obj).recordId
                 }
             }
         },

+ 2 - 1
pages-activity/noAuthority.vue

@@ -55,6 +55,7 @@ export default {
     },
     methods: {
         async handleGoApply(){
+            await this.checkUserIsBind()
             if(this.info.type=='apply'){
                 if(this.info.customer_info.has_apply){// 已经申请过
                     this.pupData.show=true
@@ -97,7 +98,7 @@ export default {
         handleBack(){
             uni.navigateBack({
                 fail:()=>{
-                    uni.switchTab({ url: '/pages/activity/activity' })
+                    uni.switchTab({ url: '/pages/report/report' })
                 }
             })
         }

+ 1 - 1
pages-applyPermission/applyPermission.vue

@@ -104,7 +104,7 @@ export default {
 
         // 上传名片
         async handleUploadCard() {
-            const res = await uploadImg()
+            const res = await uploadImg({count:1})
             this.cardImg=res[0]
         },
 

+ 65 - 20
pages-question/answerDetail.vue

@@ -11,8 +11,11 @@
               {{ questionItem.question_content }}
             </view>
             <view class="item-answer" v-if="questionItem.reply_status === 3&&!isUserResearcher">
-              <view class="answer" @click.stop="handleAudio(questionItem)">
-                <template v-if="!questionItem.loading">
+              <view class="answer" @click.stop="handlePlayAudioByBg(questionItem)">
+                <!-- 改为背景音频播放 -->
+                <image class="music-img" :src="questionItem.community_question_id==curVoiceId&&!curAudioPaused?playImgSrc:pauseImgSrc" mode="widthFix"/>
+                <text>{{ dayjs(questionItem.audio_list[0].audio_play_seconds*1000).format("mm:ss") }}</text>
+                <!-- <template v-if="!questionItem.loading">
                   <image
                     class="music-img"
                     :src="questionItem.answer.isplay ? playImgSrc : pauseImgSrc"
@@ -52,8 +55,10 @@
                   <text>{{
                     dayjs(questionItem.answer.audioTime).format("mm:ss")
                   }}</text>
-                </template>
+                </template> -->
               </view>
+              <!-- 普通用户进入的音频悬浮 -->
+              <audioBox v-if="showAudioPop"/>
             </view>
           </view>
           <text class="item-time">提问时间:{{ questionItem.create_time }}</text>
@@ -188,8 +193,8 @@
           <view class="audio-wrap">
             <view class="play">
               <van-icon
-                :name="isplay ? 'pause' : 'play'"
-                @click="handleAudioByReplay"
+                :name="questionItem.community_question_id==curVoiceId&&!curAudioPaused ? 'pause' : 'play'"
+                @click="handlePlayAudioByBg(questionItem)"
                 color="#E6B77DFF"
                 size="64rpx"
                 style="align-items: flex-start; margin-left: -20rpx"
@@ -197,9 +202,9 @@
               <!-- 进度条 -->
               <view class="slider-box">
                 <slider
-                  :value="currentAudioMsg.audioCurrentTime"
+                  :value="curTime"
                   :max="currentAudioMsg.audioTime"
-                  @change="sliderChange($event)"
+                  @change="bgAudiosliderChange($event)"
                   @changing="sliderChanging"
                   activeColor="#E6B77DFF"
                   backgroundColor="#EBEBEBFF"
@@ -208,7 +213,7 @@
                 />
                 <view class="slider-time">
                   <text>{{
-                    dayjs(currentAudioMsg.audioCurrentTime * 1000).format(
+                    dayjs(curTime*1000).format(
                       "mm:ss"
                     )
                   }}</text>
@@ -222,6 +227,11 @@
             </view>
           </view>
           <view class="audio-pub disable">已发布</view>
+
+          <!-- 音频悬浮 -->
+          <view v-show="false">
+            <audioBox v-if="showAudioPop"/>
+          </view>
         </view>
       </view>
     </template>
@@ -247,8 +257,33 @@ import { apiReplayAsk, apiGetQuestion, apiSetRead,apiCountAudioClick } from "@/a
 import { apiApplyPermission} from '@/api/user';
 import {apiGetSceneToParams} from "../api/common.js"
 import { uploadAudioToServer } from "@/utils/upload";
+import audioBox from '@/components/audioBox/audioBox.vue'
 export default {
   mixins: [mixin],
+  components:{
+    audioBox
+  },
+  computed:{
+		showAudioPop(){//是否显示音频弹窗
+      return this.$store.state.audio.show
+    },
+    showAudioBigPop(){
+      return this.$store.state.audio.showBig
+    },
+    curVoiceId(){//当前正在播放的音频id
+      return this.$store.state.audio.questionId
+    },
+    curAudioPaused(){//当前音频是否暂停状态
+      return this.$store.state.audio.paused
+    },
+    curTime(){
+      let t=0
+      if(this.questionItem?.community_question_id==this.$store.state.audio.questionId){
+        t=this.$store.state.audio.curTime
+      }
+      return t
+    }
+	},
   data() {
     return {
       questionItem: null /* {
@@ -319,6 +354,14 @@ export default {
       this.globalRecorder.stop()
     } 
   },
+  //转发分享
+	onShareAppMessage(){
+    const {community_question_id} = this.questionItem
+		return{
+			title:'问答详情',
+			path:`/pages-question/answerDetail?id=${community_question_id}`
+		}
+	},
   methods: {
     //初始化audio,onShow执行
     initAudio() {
@@ -622,10 +665,15 @@ export default {
             });
             setTimeout(() => {
               //关闭当前页面,跳转到我的回答
-              //uni.navigateBack({ delta: 1 });
-              uni.redirectTo({
-                url: '/pages-question/answerList'
-              })
+			  // 返回失败 从公众号模板消息过来的,导航至我的问答
+              uni.navigateBack({ 
+				  delta: 1 ,
+				  fail:()=>{
+					  uni.redirectTo({
+						url: '/pages-question/answerList'
+					  })
+				  }
+			  });
             }, 500);
           }
         }
@@ -686,6 +734,11 @@ export default {
     sliderChanging() {
       this.isSlider = true;
     },
+    // 背景音频播放的拖动
+    bgAudiosliderChange(e){
+      const value=e.detail.value
+      this.globalBgMusic.seek(value)
+    },
     //切换当前播放音频
     changeCurrentAudio(item) {
       const { id } = item;
@@ -759,14 +812,6 @@ export default {
           }
       }
     },
-    //转发分享
-		onShareAppMessage(){
-      const {community_question_id} = this.questionItem
-			return{
-				title:'问答详情',
-				path:`/pages-question/answerDetail?id=${community_question_id}`
-			}
-		}
   },
 };
 </script>

+ 28 - 3
pages-question/answerList.vue

@@ -32,8 +32,12 @@
               {{ item.question_content }}
             </view>
             <view class="item-answer" v-if="item.reply_status === 3">
-              <view class="answer" @click.stop="handleAudio(item)">
-                <template v-if="!item.loading">
+              <view class="answer" @click.stop="handlePlayAudioByBg(item)">
+                <!-- 改为背景音频播放 -->
+                <image class="music-img" :src="item.community_question_id==curVoiceId&&!curAudioPaused?playImgSrc:pauseImgSrc" mode="widthFix"/>
+                <text>{{ dayjs(item.answer.audioTime).format('mm:ss') }}</text>
+
+                <!-- <template v-if="!item.loading">
                   <image
                     class="music-img"
                     :src="item.answer.isplay ? playImgSrc : pauseImgSrc"
@@ -59,7 +63,7 @@
                     mode="aspectFill"
                   />
                   <text>{{ dayjs(item.answer.audioTime).format("mm:ss") }}</text>
-                </template>
+                </template> -->
               </view>
             </view>
           </view>
@@ -106,13 +110,34 @@
         </view>
       </view>
     </van-popup>
+
+    <!-- 音频悬浮 -->
+    <audioBox v-if="showAudioPop"/>
   </view>
 </template>
 <script>
 import mixin from "../mixin/questionMixin";
 import { apiBarTotal ,apiSetRead} from "@/api/question.js";
+import audioBox from '@/components/audioBox/audioBox.vue'
 export default {
   mixins: [mixin],
+  components:{
+    audioBox
+  },
+  computed:{
+		showAudioPop(){//是否显示音频弹窗
+      return this.$store.state.audio.show
+    },
+    showAudioBigPop(){
+      return this.$store.state.audio.showBig
+    },
+    curVoiceId(){//当前正在播放的音频id
+      return this.$store.state.audio.questionId
+    },
+    curAudioPaused(){//当前音频是否暂停状态
+      return this.$store.state.audio.paused
+    },
+	},
   data() {
     return {
       questionList: [],

+ 2 - 1
pages-report/chapterDetail.vue

@@ -18,7 +18,8 @@ export default {
   onShareAppMessage() {
     return {
       title:this.msgObj.title,
-      path:`/pages-report/chapterDetail?chapterId=${this.msgObj.chapterId}`
+      path:`/pages-report/chapterDetail?chapterId=${this.msgObj.chapterId}`,
+      imageUrl:this.msgObj.shareImg||''
     }
   },
   methods: {

+ 194 - 0
pages-report/previewImage.vue

@@ -0,0 +1,194 @@
+<template>
+    <view class="preview-image-page">
+        <swiper 
+            class="swiper" 
+            circular 
+            :indicator-dots="false" 
+            :autoplay="false"
+            :current="activeIndex"
+            @change="swiperChange"
+		>
+            <swiper-item v-for="item in imgList" :key="item">
+                <movable-area style="width:100%;height:100%">
+                    <movable-view 
+                        class="max" 
+                        scale
+                        direction="all"
+                        out-of-bounds
+                        style="width:100%;height:100%"
+                    >
+                        <image 
+                            class="img-item" 
+                            :src="item" mode="heightFix"
+                        />
+                    </movable-view>
+                </movable-area>
+                
+            </swiper-item>
+        </swiper>
+        <view class="bot-fix-box" :style="{bottom:isOpen?'0':'-100px'}">
+            <scroll-view 
+                class="imgs-box" 
+                scroll-x 
+                scroll-with-animation 
+                :scroll-into-view="aid"
+            >
+                <image 
+                    :id="'img'+index"
+                    v-for="(img,index) in imgList" 
+                    :key="img" 
+                    :src="img" 
+                    :class="index==activeIndex?'img-active':''"
+                    mode="heightFix"
+                    @click="handleChangeItem(index)"
+                />
+            </scroll-view>
+            <view class="open-box" @click="handleOpen">
+                <van-icon :name="isOpen?'arrow-down':'arrow-up'" color="#fff" size="25px"/>
+            </view>
+        </view>
+        <image @click="closePage" class="close-icon" src="./static/close.png" mode="aspectFill"/>
+    </view>
+</template>
+
+<script>
+import {apiReportPPtImgs} from '@/api/report'
+export default {
+    data() {
+        return {
+            isOpen:true,
+            imgList:[],
+            activeIndex:0,
+            reportId:0,
+            chapterId:0,//章节id 
+            aid:'',
+            shareTitle:'',
+            shareImg:''
+        }
+    },
+    onLoad(options){
+        console.log(options);
+        this.reportId=options.reportId
+        this.chapterId=options.chapterId
+        this.shareTitle=options.shareTitle||''
+        this.shareImg=options.shareImg||''
+        this.getReportPPtImgs()
+        uni.setPageOrientation({orientation : "landscape"})
+    },
+    onUnLoad(){
+        uni.setPageOrientation({orientation : "portrait"})
+    },
+    onShareAppMessage() {
+        let path=''
+        if(this.chapterId!=0){
+            path=`/pages-report/chapterDetail?chapterId=${this.chapterId}`
+        }else{
+            path=`/pages-report/reportDetail?reportId=${this.reportId}`
+        }
+        return {
+            title:this.shareTitle,
+            path:path,
+            imageUrl:this.shareImg
+        }
+    },
+    methods: {
+        // 获取ppt图片
+        async getReportPPtImgs(){
+            const res=await apiReportPPtImgs({
+                report_id:Number(this.reportId),
+                report_chapter_id:Number(this.chapterId)
+            })
+            if(res.code===200){
+                this.imgList=res.data||[]
+            }else{
+                uni.showToast({
+                    title: res.msg,
+                    icon: 'none'
+                })
+            }
+        },
+
+        handleChangeItem(index){
+            this.aid='img'+(index-1)
+            this.activeIndex=index 
+        },
+
+        handleOpen(){
+            this.isOpen=!this.isOpen
+        },
+        swiperChange(e){
+            this.activeIndex=e.detail.current
+        },
+        closePage(){
+            wx.navigateBack({
+                delta: 1
+            });
+        }
+    },
+}
+</script>
+
+<style>
+page{
+    padding-bottom: 0;
+}
+</style>
+<style lang="scss" scoped>
+.preview-image-page{
+    background: #333333;
+    width: 100vw;
+    height: 100vh;
+    .swiper{
+        height: 100%;
+    }
+    .img-item{
+        height: 100%;
+        display: block;
+        margin: auto;
+    }
+    .bot-fix-box{
+        position: fixed;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        height: 100px;
+        background: rgba(255, 255, 255, 0.4);
+        backdrop-filter: blur(12px);
+        transition: 0.3s;
+        display: flex;
+        .imgs-box{
+            max-width: 70%;
+            width: auto;
+            height: 100%;
+            margin: auto;
+            white-space: nowrap;
+            image{
+                height: 100%;
+            }
+            .img-active{
+                border: 1px solid #E3B377;
+            }
+        }
+        .open-box{
+            position: absolute;
+            bottom: 100%;
+            right: 20px;
+            width: 80px;
+            height: 36px;
+            background: rgba(255, 255, 255, 0.5);
+            border-radius: 10px 10px 0 0;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }
+    }
+    .close-icon{
+        position: fixed;
+        left: 100px;
+        top: 20px;
+        width: 24px;
+        height: 24px;
+        z-index: 99;
+    }
+}
+</style>

BIN
pages-report/static/close.png


+ 99 - 0
pages-roadShow/video/components/noAuth.vue

@@ -0,0 +1,99 @@
+<template>
+  <view class="voice-no-auth">
+        <image class="img" :src="globalImgUrls.activityNoAuth" mode="widthFix"></image>
+		<view style="margin-bottom:15px">您暂无权限查看线上路演</view>
+		<view v-if="info.type==='contact'" style="margin-bottom:15px">若想查看可以联系对口销售</view>
+		<view v-else style="margin-bottom:15px">若想查看可以申请开通</view>
+		<view v-if="info.type==='contact'">
+			{{info.name||''}}:<text @click="handleCall" style="color:#E3B377">{{info.mobile||''}}</text>
+		</view>
+		<view class="global-btn-yellow-change btn" @click="handleApply" v-else style="margin-top:30px">立即申请</view>
+  </view>
+</template>
+
+<script>
+import {apiApplyPermission} from '@/api/user'
+export default {    
+    props: {
+        info:null
+    },
+    watch:{
+        info(){
+            this.handleAutoApply()
+        }
+    },
+    
+    methods: {
+        handleCall(){
+
+            uni.makePhoneCall({
+                phoneNumber: this.info.mobile,
+                success: (result) => {},
+                fail: (error) => {}
+            })
+        },
+
+        handleAutoApply(){
+            if(this.info.type=='contact'&&!this.info.customer_info.has_apply){
+                if(this.info.customer_info.status=='冻结'||(this.info.customer_info.status=='试用'&&this.info.customer_info.is_suspend==1)){
+                    apiApplyPermission({
+                        company_name:this.info.customer_info.company_name,
+                        real_name:this.info.customer_info.name,
+                        source:10,
+                        from_page:'线上路演'
+                    }).then(res=>{
+                        if(res.code===200){
+                            console.log('主动申请成功');
+                        }
+                    }) 
+                }
+            }
+        },
+
+        async handleApply(){
+            await this.checkUserIsBind()
+            const {customer_info}=this.info
+            if(customer_info.has_apply){
+                uni.showToast({
+                  title:'您已提交过申请,请耐心等待',
+                  icon:'none'
+                })
+            }else{
+                if (!customer_info.status || customer_info.status != '流失') {
+                    uni.navigateTo({
+                        url: "/pages-applyPermission/applyPermission?source=10&from_page=线上路演"
+                    })
+                }else{
+                    apiApplyPermission({
+                        company_name:customer_info.company_name,
+                        real_name:customer_info.name,
+                        source:10,
+                        from_page:'线上路演'
+                    }).then(res=>{
+                        uni.navigateTo({url:'/pages-applyPermission/applyResult'})
+                    })
+                }
+            }
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.voice-no-auth{
+    padding: 34rpx;
+    text-align: center;
+    font-size: $global-font-size-lg;
+    .img{
+        width: 100%;
+        margin-bottom: 50rpx;
+    }
+    .btn{
+        width: 380rpx;
+        line-height: 70rpx;
+        margin-left: auto;
+        margin-right: auto;
+        margin-top: 40rpx;
+    }
+}
+</style>

+ 461 - 0
pages-roadShow/video/list.vue

@@ -0,0 +1,461 @@
+<template>
+    <view class="video-list-page" v-if="isAuth">
+        <van-sticky style="background: #fff">
+            <view class="flex search-wrap">
+                <view @click="goSearchPage" style="flex:1;margin-right:30rpx" >
+                    <searchBox 
+                        placeholder="关键字搜索" 
+                        :hasRightBtn="false" 
+                        :disabled="true"
+                    ></searchBox>
+                </view>
+                <view class="flex" @click="showFilter=true">
+                    <image style="width:50rpx;height:50rpx" src="@/static/filter-icon.png" mode="aspectFill"/>
+                    <text style="color:#E3B377;font-size:32rpx">筛选</text>
+                </view>
+            </view>
+        </van-sticky>
+        <view class="empty-box" v-if="list.length==0&&finished">
+            <image
+                :src="globalImgUrls.activityNoAuth"
+                mode="widthFix"
+            />
+            <view v-if="videoId!=0&&noShareData">
+                <view>暂无权限查看该视频</view>
+                <view>请刷新页面后重试</view>
+            </view>
+            <view v-else-if="videoId!=0&&!noShareData">
+                <view>该视频不存在</view>
+                <view>请刷新页面后重试</view>
+            </view>
+            <view v-else>暂无数据</view>
+        </view>
+        <view class="list-wrap">
+            <view class="item" v-for="item in list" :key="item.road_video_id">
+                <view class="title-box">
+                    <text class="tag">{{item.chart_permission_name}}</text>
+                    <text class="title">{{item.title}}</text>
+                </view>
+                <button 
+                    class="share-btn" 
+                    open-type="share" 
+                    :data-item="item">
+                    <image class="share-img" src="@/static/share-icon.png" mode="aspectFill"/>
+                </button>
+                <video
+                    autoplay
+                    object-fit="contain"
+                    show-mute-btn
+                    :poster="item.cover_img_url"
+                    :src="item.video_url"
+                    enable-play-gesture
+                    :id="item.road_video_id"
+                    @ended="handleVideoEnd"
+                    v-if="item.road_video_id==curVideoId"
+                ></video>
+                <image @click="handelClickPlay(item)" v-else class="poster" :src="item.cover_img_url" mode="aspectFill" lazy-load/>
+                <view class="time">发布时间:{{item.publish_time}}</view>
+                <view class="user-name">{{item.admin_real_name}}</view>
+            </view>
+        </view>
+
+        <!-- 筛选 -->
+        <van-popup 
+            :show="showFilter" 
+            position="bottom" 
+            :safe-area-inset-bottom="true" 
+            round 
+            @close="showFilter=false"
+        >
+            <view class="filter-wrap" @touchmove.stop>
+                <view class="flex top">
+                    <text style="color:#000">全部筛选</text>
+                    <text style="color:#E3B377" @click="showFilter=false">取消</text>
+                </view>
+                <view class="list-box">
+                    <van-collapse accordion @change="change" :value="active" :border="false">
+                        <van-collapse-item 
+                            :title="item.ClassifyName" 
+                            :name='item.ClassifyName' 
+                            :border="false"
+                            v-for="item in options"
+                            :key="item.ClassifyName"
+                        >
+                            <van-row gutter="5">
+                                <van-col 
+                                    :span="_item.PermissionName.length>7?16:8" 
+                                    v-for="_item in item.Items" 
+                                    :key="_item.PermissionId"
+                                >
+                                    <text 
+                                        :class="['list-item',_item.PermissionId==selectPerId&&'list-item-active']" 
+                                        @click="handleSelectPerItem(_item)"
+                                    >{{_item.PermissionName}}</text>
+                                </van-col>
+                            </van-row>
+                        </van-collapse-item>
+                    </van-collapse>
+                </view>
+            </view>
+        </van-popup>
+
+        <!-- 跳转去提问悬浮按钮 -->
+        <dragButton :existTabBar="true">
+            <navigator url="/pages-question/hasQuestion">
+                <view class="to-question-fixed-box">
+                    <image src="@/static/toquestion-icon.png" mode="widthFix" />
+                    <text>我要提问</text>
+                </view>
+            </navigator>
+        </dragButton>
+    </view>
+    <noAuth :info="noAuthData" v-else/>
+</template>
+<script>
+import {apiRoadShowVideoList,apiRoadShowVideoPlayLog} from '@/api/roadShow'
+import {apiGetSceneToParams,apiUserBindPermission} from '@/api/common'
+import noAuth from './components/noAuth.vue'
+import dragButton from '@/components/dragButton/dragButton.vue'
+export default {
+    components:{
+        noAuth,
+        dragButton
+    },
+    data() {
+        return {
+            showFilter:false,
+            active:'',
+            options:[],
+            selectPerId:0,
+
+            videoId:0,
+            page:1,
+            pageSize:10,
+            finished:false,
+            list:[],
+
+            curVideoId:0,
+            curVideoIns:null,
+
+            isAuth:true,
+            noAuthData:null,
+
+            noShareData:false,//用户从分享进入没有该分享的视频的权限
+        }
+    },
+    onLoad(options){
+        this.init(options)
+        this.getPermissionList()
+    },
+    onShow(){
+        //无权限时刷新列表
+        if(!this.isAuth){
+            this.getList()
+        }
+    },
+    onHide(){
+        this.showFilter=false
+        // this.curVideoId=0
+    },
+    onShareAppMessage({from,target}) {
+        console.log(from,target);
+        let path='/pages-roadShow/video/list?videoId=0'
+        let title='FICC线上路演'
+        let imageUrl=''
+        if(from=='button'){
+            title=target.dataset.item.title
+            path=`/pages-roadShow/video/list?videoId=${target.dataset.item.road_video_id}`
+            imageUrl=target.dataset.item.cover_img_url
+        }
+        return {
+            title:title,
+            path:path,
+            imageUrl:imageUrl
+        }
+    },
+    onPullDownRefresh(){
+        this.videoId=0
+        this.noShareData=false
+        this.selectPerId=0
+        this.page=1
+        this.list=[]
+        this.finished=false
+        this.getList()
+        setTimeout(() => {
+            uni.stopPullDownRefresh()
+        }, 1500)
+    },
+    onReachBottom() {
+        if(this.finished) return
+        this.page++
+        this.getList()
+    },
+    methods: {
+        async init(options){
+            if(options.scene){
+                const resScene=await apiGetSceneToParams({scene_key:options.scene})
+                if(resScene.code===200){
+                    const obj=JSON.parse(resScene.data)
+                    this.videoId=obj.videoId||0
+                }
+            }else{
+                this.videoId=options.videoId||0
+            }
+            this.getList()
+        },
+
+        goSearchPage(){
+            uni.navigateTo({
+                url: '/pages-roadShow/video/search',
+            });
+        },
+
+        change(e){
+            this.active=e.detail
+        },
+
+        //点击分类某项
+        handleSelectPerItem(item){
+            if(this.selectPerId==item.PermissionId){
+                this.selectPerId=0
+            }else{
+                this.selectPerId=item.PermissionId
+            }
+            
+            this.videoId=0//重置掉分享进入的状态
+            this.noShareData=false
+            this.curVideoId=0
+            this.page=1
+            this.list=[]
+            this.finished=false
+            this.getList()
+            this.showFilter=false
+        },
+
+        async getList(){
+            const res=await apiRoadShowVideoList({
+                page_index:Number(this.page),
+                page_size:Number(this.pageSize),
+                video_id:Number(this.videoId),
+                chart_permission_id:Number(this.selectPerId)
+            })
+            if(res.code===200){
+                let arr=res.data.list||[]
+                this.list=[...this.list,...arr]
+                if(res.data.paging.is_end){
+                    this.finished=true
+                }
+                this.isAuth=true
+            }else if(res.code===403){
+                //无权限用户
+                this.isAuth=false
+                this.noAuthData=res.data
+            }else if(res.code===4001){
+                // 用户从分享进入没有这个视频的权限
+                this.noShareData=true
+                this.finished=true
+            }
+        },
+
+        //获取筛选项
+        async getPermissionList(){
+            const res=await apiUserBindPermission()
+            if(res.code===200){
+                const result = res.data.permission_list||[];
+                this.options = result.map(item=>{
+                    let obj = {};
+                    obj.ClassifyName = item.classify_name;
+                    obj.Items = item.list.map(_item=>{
+                        return {PermissionId:_item.chart_permission_id,PermissionName:_item.chart_permission_name};
+                    })
+                    return obj;
+                })
+            }
+        },
+
+        handelClickPlay(item){
+            this.curVideoId=item.road_video_id
+            setTimeout(() => {
+                this.curVideoIns=uni.createVideoContext(this.curVideoId.toString())
+            }, 300);
+            // 记录播放
+            apiRoadShowVideoPlayLog({video_id:Number(item.road_video_id)}).then(res=>{
+                if(res.code===200){
+                    console.log('视频埋点成功');
+                }
+            })
+        },
+
+        //视频播放结束
+        handleVideoEnd(){
+            // 此处因为如果不调用退出全屏方法 安卓和ios页面均会表现异常,安卓横屏不恢复竖屏,ios底部tabbar渲染异常
+            this.curVideoIns.exitFullScreen()
+            setTimeout(() => {
+                this.curVideoId=0
+                this.curVideoIns=null
+            }, 200);
+        }
+    },
+}
+</script>
+
+<style lang='scss'>
+.search-wrap .van-search{
+    padding: 0 !important;
+}
+
+.video-list-page{
+    .filter-wrap{
+        .van-cell__title, .van-cell__value{
+            flex: none !important;
+        }
+        .van-cell:after{
+            border: none !important;
+        }
+        .van-cell__title{
+            font-size: 14px;
+        }
+        .van-hairline--top:after{
+            border-top-width: 0 !important;
+        }
+    }
+}
+</style>
+<style lang="scss" scoped>
+.video-list-page{
+    .search-wrap {
+        background-color: #fff;
+        padding: 30rpx 34rpx;
+        align-items: center;
+        .menu-icon{
+            width: 52rpx;
+            height: 40rpx;
+            display: block;
+            flex-shrink: 0;
+            margin-left: 30rpx;
+        }
+    } 
+    
+    .list-wrap{
+        .item{
+            border-top: 10rpx solid #f9f9f9;
+            padding: 30rpx 34rpx;
+            position: relative;
+            .title-box{
+                padding-right: 40rpx;
+            }
+            .share-btn{
+                position: absolute;
+                top: 34rpx;
+                right: 34rpx;
+                background-color: transparent;
+                width: 36rpx;
+                height: 36rpx;
+                line-height: 1;
+                padding: 0;
+                &::after{
+                    border: none;
+                }
+            }
+            .share-img{
+                width: 32.5rpx;
+                height: 32rpx;
+            }
+            .tag{
+                color: #E4B478;
+                background-color: #333;
+                padding: 5rpx 20rpx;
+                border-radius: 20rpx;
+                font-size: 24rpx;
+                margin-right: 26rpx;
+                max-width: 250rpx;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                white-space: nowrap;
+                display: inline-block;
+                vertical-align: middle;
+            }
+            .title{
+                font-size: 32rpx;
+                color: #000;
+                vertical-align: middle;
+            }
+            video{
+                width: 100%;
+                height: 400rpx;
+                margin: 30rpx 0 20rpx 0;
+                border-radius: 20rpx;
+            }
+            .poster{
+                width: 100%;
+                height: 400rpx;
+                margin: 30rpx 0 20rpx 0;
+                border-radius: 20rpx;
+                position: relative;
+                &::after{
+                    content:'';
+                    display: block;
+                    position: absolute;
+                    width: 120rpx;
+                    height: 120rpx;
+                    top: 50%;
+                    left: 50%;
+                    transform: translate(-50%,-50%);
+                    background-image: url('@/static/video-play-btn.png');
+                    background-size: cover;
+                }
+            }
+            .time{
+                font-size: 28rpx;
+                color: #999;
+                display: inline-block;
+            }
+            .user-name{
+                float: right;
+                font-size: 28rpx;
+                color: #999;
+            }
+        }
+    }
+
+    .empty-box{
+        text-align: center;
+        font-size: 32rpx;
+        color: #999;
+        padding-top: 150rpx;
+        image{
+            width: 80vw;
+            margin-bottom: 57rpx;
+        }
+    }
+}
+
+.filter-wrap{
+    background-color: #fff;
+    padding-top: 53rpx;
+    padding-bottom: 100rpx;
+    .top{
+        font-size: 32rpx;
+        justify-content: space-between;
+        margin-bottom: 40rpx;
+        padding: 0 34rpx;
+    }
+    .list-box{
+        min-height: 30vh;
+        max-height: 60vh;
+        .list-item{
+            display: block;
+            margin: 10rpx;
+            height: 76rpx;
+            line-height: 76rpx;
+            color: #000;
+            background: #F6F6F6;
+            border-radius: 4px 4px 4px 4px;
+            text-align: center;
+        }
+        .list-item-active{
+            background-color: #FAEEDE;
+        }
+    }
+}
+</style>

+ 267 - 0
pages-roadShow/video/search.vue

@@ -0,0 +1,267 @@
+<template>
+    <view class="video-search-page">
+        <van-sticky style="background: #fff">
+            <view style="padding:30rpx 34rpx">
+            <searchBox 
+                :focus="focus" 
+                placeholder="关键字搜索" 
+                @change="onChange"
+                @search="onSearch"
+            ></searchBox>
+            </view>
+        </van-sticky>
+        <view class="empty-box" v-if="list.length==0&&finished">
+            <image
+                :src="globalImgUrls.activityNoAuth"
+                mode="widthFix"
+            />
+            <view>暂无数据,试试别的搜索词吧~</view>
+        </view>
+        <view class="list-wrap">
+            <view class="item" v-for="item in list" :key="item.road_video_id">
+                <view class="title-box">
+                    <text class="tag">{{item.chart_permission_name}}</text>
+                    <text class="title">{{item.title}}</text>
+                </view>
+                <button 
+                    class="share-btn" 
+                    open-type="share" 
+                    :data-item="item">
+                    <image class="share-img" src="@/static/share-icon.png" mode="aspectFill"/>
+                </button>
+                <video
+                    autoplay
+                    object-fit="contain"
+                    show-mute-btn
+                    :poster="item.cover_img_url"
+                    :src="item.video_url"
+                    enable-play-gesture
+                    :id="item.road_video_id"
+                    @ended="handleVideoEnd"
+                    v-if="item.road_video_id==curVideoId"
+                ></video>
+                <image @click="handelClickPlay(item)" v-else class="poster" :src="item.cover_img_url" mode="aspectFill" lazy-load/>
+                <view class="time">发布时间:{{item.publish_time}}</view>
+                <view class="user-name">{{item.admin_real_name}}</view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+import searchBox from '@/components/searchBox/searchBox.vue'
+import {apiRoadShowVideoList,apiRoadShowVideoPlayLog} from '@/api/roadShow'
+export default {
+    components: {
+        searchBox
+    },
+    data() {
+        return {
+            searchVal:'',
+            focus:true,
+            list:[],
+            finished:false,
+            page:1,
+            pageSize:10,
+
+            curVideoId:0,
+            curVideoIns:null
+        }
+    },
+    onReachBottom() {
+        if(this.finished) return
+        this.page++
+        this.getList()
+    },
+    onShareAppMessage({from,target}) {
+        console.log(from,target);
+        let path='/pages-roadShow/video/list?videoId=0'
+        let title='FICC线上路演'
+        let imageUrl=''
+        if(from=='button'){
+            title=target.dataset.item.title
+            path=`/pages-roadShow/video/list?videoId=${target.dataset.item.road_video_id}`
+            imageUrl=target.dataset.item.cover_img_url
+        }
+        return {
+            title:title,
+            path:path,
+            imageUrl:imageUrl
+        }
+    },
+    methods: {
+        onChange(e){
+            this.searchVal=e
+        },
+
+        async onSearch(){
+            this.finished=false
+            this.page=1
+            this.list=[]
+            if(!this.searchVal){
+                this.finished=true
+                return
+            }
+            this.getList()
+        },
+
+        async getList(){
+            this.curVideoId=0
+            const res=await apiRoadShowVideoList({
+                page_index:Number(this.page),
+                page_size:Number(this.pageSize),
+                keywords:this.searchVal
+            })
+            if(res.code===200){
+                let arr=res.data.list||[]
+                this.list=[...this.list,...arr]
+                if(res.data.paging.is_end){
+                    this.finished=true
+                }
+            }
+        },
+
+        handelClickPlay(item){
+            this.curVideoId=item.road_video_id
+            setTimeout(() => {
+                this.curVideoIns=uni.createVideoContext(this.curVideoId.toString())
+            }, 300);
+            // 记录播放
+            apiRoadShowVideoPlayLog({video_id:Number(item.road_video_id)}).then(res=>{
+                if(res.code===200){
+                    console.log('视频埋点成功');
+                }
+            })
+        },
+
+        //视频播放结束
+        handleVideoEnd(){
+            // 此处因为如果不调用退出全屏方法 安卓和ios页面均会表现异常,安卓横屏不恢复竖屏,ios底部tabbar渲染异常
+            this.curVideoIns.exitFullScreen()
+            setTimeout(() => {
+                this.curVideoId=0
+                this.curVideoIns=null
+            }, 200);
+        }
+    },
+}
+</script>
+
+<style>
+.van-sticky-wrap--fixed{
+  background-color: #fff;
+}
+page{
+    padding-bottom: 0;
+}
+</style>
+
+<style lang="scss" scoped>
+.video-search-page{
+    .search-wrap {
+        background-color: #fff;
+        padding: 30rpx 34rpx;
+        align-items: center;
+        .menu-icon{
+            width: 52rpx;
+            height: 40rpx;
+            display: block;
+            flex-shrink: 0;
+            margin-left: 30rpx;
+        }
+    } 
+    
+    .list-wrap{
+        .item{
+            border-top: 10rpx solid #f9f9f9;
+            padding: 30rpx 34rpx;
+            position: relative;
+            .title-box{
+                padding-right: 40rpx;
+            }
+            .share-btn{
+                position: absolute;
+                top: 34rpx;
+                right: 34rpx;
+                background-color: transparent;
+                width: 36rpx;
+                height: 36rpx;
+                line-height: 1;
+                padding: 0;
+                &::after{
+                    border: none;
+                }
+            }
+            .share-img{
+                width: 32.5rpx;
+                height: 32rpx;
+            }
+            .tag{
+                color: #E4B478;
+                background-color: #333;
+                padding: 5rpx 20rpx;
+                border-radius: 20rpx;
+                font-size: 24rpx;
+                margin-right: 26rpx;
+                max-width: 250rpx;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                white-space: nowrap;
+                display: inline-block;
+                vertical-align: middle;
+            }
+            .title{
+                font-size: 32rpx;
+                color: #000;
+                vertical-align: middle;
+            }
+            video{
+                width: 100%;
+                height: 400rpx;
+                margin: 30rpx 0 20rpx 0;
+                border-radius: 20rpx;
+            }
+            .poster{
+                width: 100%;
+                height: 400rpx;
+                margin: 30rpx 0 20rpx 0;
+                border-radius: 20rpx;
+                position: relative;
+                &::after{
+                    content:'';
+                    display: block;
+                    position: absolute;
+                    width: 120rpx;
+                    height: 120rpx;
+                    top: 50%;
+                    left: 50%;
+                    transform: translate(-50%,-50%);
+                    background-image: url('@/static/video-play-btn.png');
+                    background-size: cover;
+                }
+            }
+            .time{
+                font-size: 28rpx;
+                color: #999;
+                display: inline-block;
+            }
+            .user-name{
+                float: right;
+                font-size: 28rpx;
+                color: #999;
+            }
+        }
+    }
+
+    .empty-box{
+        text-align: center;
+        font-size: 32rpx;
+        color: #999;
+        padding-top: 150rpx;
+        image{
+            width: 80vw;
+            margin-bottom: 57rpx;
+        }
+    }
+}
+</style>

+ 55 - 12
pages-sandTable/sandTable.vue

@@ -34,7 +34,7 @@
 		</block>
 		<!-- 有权限 -->
 		<view v-show="noAuthor ==0 && isFinished" style="padding-top: 156rpx;">
-			<view style="position: fixed;top: 0;width: 100vw;">
+			<view style="position: fixed;top: 0;width: 100vw;z-index: 2;">
 				<view style="height: 2rpx;background-color: #F6F6F6;"></view>
 				  <view class="search-wrap">
 					  <van-search
@@ -72,8 +72,14 @@
 							<rich-text :nodes="item.name" class="sandTable-name"></rich-text>
 						</view>
 						<view class="sandTable-box-topR">
-							<image src="../static/sandTable/fullScreen.png" style="margin-right: 50rpx;" @click="previewImage(index)"></image>
-							<image src="../static/sandTable/sandTable-share.png" @click="generatePoster('detail',item)"></image>
+							<image src="../static/sandTable/fullScreen.png" style="margin-right: 32rpx;" @click="previewImage(index)"></image>
+							<image src="@/static/sandTable/sandBox-share-icon.png" style="margin-right: 32rpx;" @click="generatePoster('detail',item)"></image>
+							<button 
+								class="share-btn" 
+								open-type="share" 
+								:data-item="item">
+								<image src="../static/sandTable/sandTable-share.png" mode="aspectFill"/>
+							</button>
 						</view>
 					</view>
 					<view class="sandTable-item" @click="previewImage(index)">
@@ -186,7 +192,7 @@
 		onLoad(options) {
 			this.init(options)
 		},
-		onShow(options) {
+		onShow() {
 			// 预览图片结束时,会触发onShow,切回竖屏
 			uni.setPageOrientation({orientation : "portrait"})
 			if(this.haveGoToResult){
@@ -196,13 +202,36 @@
 			}
 		},
 		// 小程序自带分享
-		onShareAppMessage() {
-			let {keyword,chart_permission_id,is_high_light} = this.sandTableQuery
-		    return {
-		        title:'沙盘推演',
-				path:`/pages-sandTable/sandTable?keyword=${keyword}&chart_permission_id=${chart_permission_id}`+
-				`&is_high_light=${is_high_light}&firstClassifyId=${this.selectedFirstId}`
-		    }
+		onShareAppMessage({from,target}) {
+			if(from == 'menu'){
+				// 列表分享
+				let {keyword,chart_permission_id,is_high_light} = this.sandTableQuery
+				return {
+				    title:'沙盘推演',
+					path:`/pages-sandTable/sandTable?keyword=${keyword}&chart_permission_id=${chart_permission_id}`+
+					`&is_high_light=${is_high_light}&firstClassifyId=${this.selectedFirstId}`
+				}
+			}else if(from == 'button'){
+				// 单个分享
+				let {sandbox_id,chart_permission_id,chart_permission_name,name,pic_url} = target.dataset.item
+				let first_permission_id = 0
+				// 通过分享的沙盘图的二级分类Id,找到对应的一级Id
+				U:for (let item of this.classfyList) {
+					for (let it of item.list) {
+						if(it.chart_permission_id == chart_permission_id){
+							first_permission_id = item.id
+							break U
+						}
+					}
+				}
+				return {
+				    title:`${chart_permission_name}: ${name}`,
+					path:`/pages-sandTable/sandTable?sandbox_id=${sandbox_id}&chart_permission_id=${chart_permission_id}`+
+					`&firstClassifyId=${first_permission_id}`,
+					imageUrl:pic_url
+				}
+			}
+
 		},
 		onPullDownRefresh() {
 			this.sandTableQuery.curr_page=1
@@ -232,10 +261,13 @@
 			async init(options){
 				let obj={}
 				if(options.scene){
+					// 海报分享
 					let res = await apiGetSceneToParams({scene_key:options.scene})
 					if(res.code==200){
 						obj=JSON.parse(res.data)
 					}
+				}else{
+					obj = options
 				}
 				// 有sandbox_id 说明是单个分享进来的
 				if(obj.sandbox_id){
@@ -354,7 +386,8 @@
 				})
 			},
 			// 权限申请
-			toApply(type=''){
+			async toApply(type=''){
+				await this.checkUserIsBind()
 				if(type=='auto'){
 					apiApplyPermission({
 						company_name:this.applyer.company,
@@ -636,6 +669,16 @@
 				.sandTable-box-topR{
 					display: flex;
 					align-items: center;
+					.share-btn{
+						padding: 0;
+						height: unset;
+						line-height: unset;
+						font-size: 0;
+						border-radius: unset;
+						&::after{
+							border: none;
+						}
+					}
 					image{
 						width: 32rpx;
 						height: 32rpx;

+ 335 - 51
pages-voice/addVoice.vue

@@ -29,6 +29,24 @@
             @click-icon="showFilter=true"
         />
 
+        <!-- 上传图片部分 -->
+        <view 
+            class="flex upload-img-box" 
+            v-if="recorderStatus!=='doing'&&recorderStatus!=='pause'"
+            :style="[{'height':recorderStatus==='stop'&&'auto'},{'border-bottom':recorderStatus==='stop'?'1px solid #E6E6E6':''}]"
+        >
+            <view class="item" v-for="(item,index) in imgList" :key="item" @click="handlePreViewImg(item)">
+                <image :src="item" mode="aspectFill"/>
+                <view class="del-btn" @click.stop="handleDelImg(index)">
+                    <van-icon name="cross" size="12" style="position: absolute;left:16rpx;top:8rpx" color="#ffffff"/>
+                </view>
+            </view>
+            <view class="item add-btn" @click="handleUploadImg" v-if="imgList.length<5">
+                <image src="./static/camera.png" mode="aspectFill" />
+            </view>
+              
+        </view>
+
         <view class="flex audio-box" v-if="recorderStatus==='stop'">
             <image 
                 :src="temAudio.paused?'../../../static/voice/pause.png':'../../../static/voice/playing.png'" 
@@ -49,14 +67,14 @@
                 <image src="./static/del.png" mode="aspectFill" />
                 <text>删除</text>
             </view>
-              
+            <view class="pre_publish_time" v-if="voiceId!=0">定时发布时间:{{pre_publish_time}}</view>
         </view>
           
 
-        <view class="empty-voice-box" v-if="recorderStatus==='start'">
+        <!-- <view class="empty-voice-box" v-if="recorderStatus==='start'">
             <image src="./static/record.png" mode="aspectFill" />
             <view>无录音(录音时长超过十分钟自动结束)</view>
-        </view>
+        </view> -->
 
         <view class="animat-box" v-if="recorderStatus==='doing'||recorderStatus==='pause'">
             <view class="con-box">
@@ -65,10 +83,10 @@
                 <image :class="['img move3',recorderStatus==='doing'?'animat-run':'animat-pause']" src="./static/record-img.png" mode="widthFix" />
             </view>
             <view class="bot-text">{{time|formatTime}}</view>
-        </view>
+        </view> 
 
         <view class="sound-record-wrap" v-if="recorderStatus!=='stop'">
-            <view class="top-text">点击开始录音</view>
+            <view class="top-text" v-show="recorderStatus=='start'">点击开始录音</view>
             <image class="btn" :src="btnImg" mode="aspectFill" @click="handleClickBtn" />
             <view 
                 class="del-btn" 
@@ -81,9 +99,13 @@
                 v-if="recorderStatus!=='start'"
                 @click="handleEndRecorder"
             >完成</view>
+            <view style="text-align:center;color:#999;margin-top:44rpx" v-if="recorderStatus==='start'">无录音(录音时长超过十分钟自动结束)</view>
         </view>
 
-        <view class="publish-btn" v-if="recorderStatus==='stop'" @click="handlePublish">发布</view>
+        <view class="publish-btn" v-if="recorderStatus==='stop'">
+            <text @click="handlePublish('time')">定时发布</text>
+            <text @click="handlePublish">立即发布</text>
+        </view>
         
         <!-- 筛选弹窗 -->
         <van-popup 
@@ -109,14 +131,68 @@
                 />
             </view>
         </van-popup>
+
+        <!-- 选择时间弹窗 -->
+        <van-popup 
+            :show="showTime" 
+            position="bottom"  
+            :close-on-click-overlay="true"
+            @close="showTime = false"
+            round
+        >
+            <van-datetime-picker
+                title="设置发布时间"
+                type="datetime"
+                :value="selectTime"
+                :min-date="minDate"
+                @confirm="handleConfirmTime"
+                @cancel="showTime = false"
+            />
+        </van-popup>
+
+        <!-- <van-dialog id="van-dialog" /> -->
+        <!-- 发布弹窗 -->
+        <van-dialog
+            use-slot
+            :show="showPublish"
+            confirm-button-text="发布且推送"
+            cancel-button-text="仅发布"
+            show-cancel-button
+            @confirm="handleConfirmPublish(1)"
+            @cancel="handleConfirmPublish(0)"
+            @close="showPublish=false"
+        >
+            <view style="padding:40px 20px 20px;text-align:center;position: relative;">
+                发布后将推送模板消息和客群,确认发布吗?
+                <van-icon name="cross" size="20" style="position: absolute;right:10px;top:10px" color="#6666" @click="showPublish=false"/>
+            </view>
+        </van-dialog>
+
+        <!-- 定时发布提示弹窗 -->
+        <van-dialog
+            use-slot
+            :show="showTimeAttention"
+            confirm-button-text="知道了"
+            confirm-button-color="#E3B377"
+            @confirm="showTime=true"
+            @close="showTimeAttention=false,showTime=true"
+        >
+            <view style="padding:40px 20px 20px;text-align:center;position: relative;">
+                设置定时发布后,会在设定的时间发布语音播报并推送模板消息
+                <van-icon name="cross" size="20" style="position: absolute;right:10px;top:10px" color="#666" @click="showTimeAttention=false,showTime=true"/>
+            </view>
+        </van-dialog>
     </view>
 </template>
 
 <script>
-import {apiVoiceSectionList} from '@/api/voice'
+import {apiVoiceSectionList,apiVoiceSendMsg,apiVoiceAdd,apiVoiceEdit,apiVoicePublish,apiVoiceDetail} from '@/api/voice'
 import {baseApiUrl} from '@/utils/config.js'
+import {apiGetSceneToParams} from '@/api/common'
 import CryptoJS from '@/utils/crypto.js'
 import uniAsync from "@/utils/uni-async.js"; // uni api async 化
+import {uploadImg,commonUploadAudio} from '@/utils/upload'
+const dayjs=require('@/utils/dayjs.min')
 const recorderManager = wx.getRecorderManager();//录音实例
 let innerAudioContext = uni.createInnerAudioContext();//播放音频实例
 let TIMER=null//计时器
@@ -149,15 +225,18 @@ export default {
                 variety_id:'',
                 section_id:'',
                 section_name:'',
-                img_url:''
+                img_url:'',//分享时的底图
             },
+            voiceId:0,
+            voiceUrl:'',//编辑音频地址
+            pre_publish_time:'',
 
             recorderStatus:'start',//当前录音状态 start开始 doing正在录音 stop停止录音 pause录音暂停
             time:0,
             isReset:false,//是否点击了重置
 
             temAudio:{
-                url:'',//临时音频地址
+                url:'',//音频地址
                 duration:'',//时长
                 size:'',//大小
                 curTime:0,//播放时当前播放的时间
@@ -169,17 +248,25 @@ export default {
             mainActiveIndex:0,
             activeId:0,//选择的板块id
 
+            showPublish:false,
+
+            imgList:[],
+            showTime:false,
+            showTimeAttention:false,
+            selectTime:new Date().getTime(),
+            minDate:new Date().getTime()+600000,
         }
     },
-    onLoad(){
+    onLoad(options){
         // 调取用户授权使用麦克风
         uni.authorize({
             scope: 'scope.record',
             success() {}
         })
         this.listenVoice()
-        
         this.getOptionsList()
+
+        this.init(options)
     },
     onShow(){
         innerAudioContext = uni.createInnerAudioContext()
@@ -191,8 +278,45 @@ export default {
     },
     onUnload(){
         innerAudioContext.destroy()
+        clearInterval(TIMER)
 	},
     methods: {
+        async init(options){
+            if(options.scene){  
+                const res=await apiGetSceneToParams({scene_key:options.scene})
+                if(res.code===200){
+                    const obj=JSON.parse(res.data)
+                    this.voiceId=Number(obj.voiceId)
+                    this.getVoiceDetail()
+                }
+            }else if(options.voiceId){
+                this.voiceId=Number(options.voiceId)
+                this.getVoiceDetail()
+            }
+        },
+
+        //语音详情
+        async getVoiceDetail(){
+            uni.setNavigationBarTitle({ title: '编辑语音' })
+            const res=await apiVoiceDetail({broadcast_id:Number(this.voiceId)})
+            if(res.code===200){
+                this.form.title=res.data.BroadcastName
+                this.form.variety_name=res.data.VarietyName
+                this.form.variety_id=res.data.VarietyId
+                this.form.section_id=res.data.SectionId
+                this.form.section_name=res.data.SectionName
+                this.voiceUrl=res.data.VoiceUrl
+                this.temAudio.url=res.data.VoiceUrl
+                this.temAudio.duration=res.data.VoicePlaySeconds
+                this.temAudio.size=res.data.VoiceSize
+                this.activeId=res.data.SectionId
+                this.imgList=res.data.Imgs||[]
+                this.pre_publish_time=dayjs(res.data.PrePublishTime).format('YYYY-MM-DD HH:mm')
+                this.selectTime=dayjs(res.data.PrePublishTime).valueOf()
+                this.recorderStatus='stop'
+            }
+        },
+
         //录音事件
         listenVoice(){
             recorderManager.onStart(()=>{
@@ -298,6 +422,7 @@ export default {
         //点击播放\暂停音频
         handlePlayAudio(){
             innerAudioContext.src=this.temAudio.url
+            innerAudioContext.obeyMuteSwitch=false
             if(this.temAudio.paused){
                 innerAudioContext.play()
             }else{
@@ -390,7 +515,7 @@ export default {
         },
 
         // 发布
-        handlePublish(){
+        async handlePublish(type){
             if(!this.form.title||!this.form.variety_id){
                 uni.showToast({
 					title:'请将内容填写完整',
@@ -398,29 +523,68 @@ export default {
 				})
                 return
             }
-            let formData={
+
+            if(type==='time'){//定时发布
+                this.showTimeAttention=true
+            }else{
+                this.showPublish=true
+            }
+        },
+
+        //确认发布 publishType 发布类型: 0-仅发布 1-发布并推送 2-定时发布
+        async handleConfirmPublish(publishType){
+
+            //上传音频
+            if(this.temAudio.url!=this.voiceUrl){
+                const voiceRes=await commonUploadAudio(this.temAudio.url)
+                if(voiceRes.code===200){
+                    this.temAudio.url=voiceRes.data.audio_url
+                }
+            }
+            
+            //新增、编辑语音
+            let params={
                 broadcast_name:this.form.title,
                 section_id:Number(this.form.section_id),
                 section_name:this.form.section_name,
                 variety_id:Number(this.form.variety_id),
                 variety_name:this.form.variety_name,
-                img_url:this.form.img_url,
                 author_id:Number(this.$store.state.user.userInfo.user_id),
-                author:this.$store.state.user.userInfo.real_name
+                author:this.$store.state.user.userInfo.real_name,
+                imgs:this.imgList.join(','),
+                voice_seconds:this.temAudio.duration.toString(),
+                voice_size:this.temAudio.size.toString(),
+                voice_url:this.temAudio.url
             }
-            uni.uploadFile({
-                url: baseApiUrl + "/voice/broadcast/add",
-                filePath: this.temAudio.url,
-                name: 'file',
-                header: {
-                    Authorization: this.$store.state.user.token,
-                },
-                formData: formData,
-                success: (result) => {
-                    const { envVersion } = uni.getAccountInfoSync().miniProgram
-                    const res =  envVersion === 'release' ? JSON.parse(CryptoJS.Des3Decrypt(result.data)) :  JSON.parse(result.data);
-                    console.log(res);
-                    if(res.code===200){
+            wx.showLoading({
+                title: '发布中...',
+                mask: true,
+                success: (result) => {},
+                fail: () => {},
+                complete: () => {}
+            });
+            let addRes=null
+            if(this.voiceId!=0){//编辑语音
+                params.broadcast_id=this.voiceId
+                addRes=await apiVoiceEdit(params)
+            }else{
+                addRes=await apiVoiceAdd(params)
+            }
+            if(addRes.code===200){
+                let par={
+                    broadcast_id:addRes.data.BroadcastId,
+                }
+                if(publishType===2){//定时发布
+                    par.publish_type=2
+                    par.pre_publish_time=dayjs(this.selectTime).format('YYYY-MM-DD HH:mm:ss')
+                }else{
+                    par.publish_type=1
+                }
+                //发布语音
+                const publishRes=await apiVoicePublish(par)
+                wx.hideLoading();
+                if(publishRes.code===200){
+                    if(publishType==0){//仅发布
                         uni.showToast({
                             title:'发布成功',
                             icon:'success'
@@ -429,26 +593,82 @@ export default {
                             uni.$emit('addVoiceSuccess')
                             uni.navigateBack()
                         }, 1000);
-                    }else{
+                    }else if(publishType==1){//发布并推送
+                        this.handleSendMsg(addRes.data.BroadcastId)
+                    }else{////定时发布
                         uni.showToast({
-                            title:res.msg,
-                            icon:'none'
+                            title:'定时发布成功',
+                            icon:'success'
                         })
+                        setTimeout(() => {
+                            uni.$emit('addVoiceSuccess')
+                            uni.navigateBack()
+                        }, 1000);
                     }
-                },
-                fail: () => {
-                    console.log('发布失败');
+                }else{
                     uni.showToast({
-                        title:'发布失败,请稍后重试!',
+                        title:publishRes.msg,
                         icon:'none'
                     })
-                },
-                complete: () => {
-                    console.log('con');
                 }
-            });
-              
-        }
+            }else{
+                wx.hideLoading();
+                uni.showToast({
+                    title:addRes.msg,
+                    icon:'none'
+                })
+            }
+        },
+
+        //推送消息
+        handleSendMsg(id){
+            apiVoiceSendMsg({broadcast_id:id}).then(res=>{
+                if(res.code===200){
+                    uni.showToast({
+                        title:"发布&推送成功",
+                        icon:'success'
+                    })
+                    setTimeout(() => {
+                        uni.$emit('addVoiceSuccess')
+                        uni.navigateBack()
+                    }, 1000);
+                }
+            })
+        },
+
+        //图片上传
+        handleUploadImg(e){
+            uploadImg({count:5-this.imgList.length}).then(res=>{
+                this.imgList=[...this.imgList,...res]
+            }).catch(err=>{
+                console.log(err);
+                if(err.errMsg=='chooseImage:fail cancel') return
+                uni.showToast({
+                    title:'上传失败,请重试!',
+                    icon:'none'
+                })
+            })
+        },
+
+        //预览图片
+        handlePreViewImg(item){
+            wx.previewImage({
+                urls:this.imgList,
+                current:item
+            })
+        },
+
+        //删除图片
+        handleDelImg(index){
+            this.imgList.splice(index,1)
+        },
+
+        //选择时间
+        handleConfirmTime(e){
+            this.selectTime=e.detail
+            this.showTime=false
+            this.handleConfirmPublish(2)
+        },
 
     },
 }
@@ -508,7 +728,7 @@ page{
 <style lang="scss" scoped>
 .add-voice-page{
     .empty-voice-box{
-        height: 50vh;
+        height: 36vh;
         padding-top: 150rpx;
         image{
             width: 140rpx;
@@ -563,6 +783,7 @@ page{
         margin-bottom: 180rpx;
         padding: 0 30rpx;
         position: relative;
+        border-radius: 16rpx;
         .left-time{
             position: absolute;
             bottom: 20rpx;
@@ -601,19 +822,82 @@ page{
                 margin-right: 10rpx;
             }
         }
+        .pre_publish_time{
+            position: absolute;
+            bottom: -50rpx;
+            left: 0;
+            color: #999999;
+        }
     }
 
     .publish-btn{
-        width: 390rpx;
-        height: 80rpx;
+        // width: 390rpx;
+        // height: 80rpx;
+        // text-align: center;
+        // line-height: 80rpx;
+        // color: #fff;
+        // background: #E6B77D;
+        // font-size: 32rpx;
+        // border-radius: 40px;
+        // margin-left: auto;
+        // margin-right: auto;
         text-align: center;
-        line-height: 80rpx;
-        color: #fff;
-        background: #E6B77D;
-        font-size: 32rpx;
-        border-radius: 40px;
-        margin-left: auto;
-        margin-right: auto;
+        text{
+            width: 300rpx;
+            height: 80rpx;
+            text-align: center;
+            line-height: 80rpx;
+            display: inline-block;
+            font-size: 32rpx;
+            margin: 0 15rpx;
+            border-radius: 40rpx;
+        }
+        text:first-child{
+            border: 1px solid #E6B77D;
+            color: #E6B77D;
+        }
+        text:last-child{
+            background-color: #E6B77D;
+            color: #fff;
+        }
+    }
+
+    .upload-img-box{
+        padding: 34rpx 4rpx 34rpx 34rpx;
+        flex-wrap: wrap;
+        height: 50vh;
+        align-content: flex-start;
+        .item{
+            width: 200rpx;
+            height: 200rpx;
+            margin-right: 30rpx;
+            margin-bottom: 30rpx;
+            flex-shrink: 0;
+            position: relative;
+            image{
+                width: 100%;
+                height: 100%;
+            }
+            .del-btn{
+                position: absolute;
+                right: 0;
+                top: 0;
+                width: 48rpx;
+                height: 48rpx;
+                background-color: rgba(0, 0, 0, 0.7);
+                border-radius: 0 0 0 48rpx;
+            }
+        }
+        .add-btn{
+            background-color: #f7f7f7;
+            image{
+                width: 80rpx;
+                height: 80rpx;
+                position: absolute;
+                left: 60rpx;
+                top: 60rpx;
+            }
+        }
     }
 }
 

+ 450 - 0
pages-voice/myVoice.vue

@@ -0,0 +1,450 @@
+<template>
+    <view class="my-voice-page">
+        <view class="top-tab-warp">
+           <text 
+            :class="['item',status==opt.value?'item-active':'']" 
+            v-for="opt in statusOpt" 
+            :key="opt.value"
+            @click="handleOptChange(opt)"
+        >{{opt.lable}}({{opt.count}})</text>
+        </view>
+
+        <view class="empty-box" v-if="finished&&list.length==0">
+            <image :src="globalImgUrls.activityNoAuth" mode="widthFix" />
+            <view>暂无数据</view>
+        </view>
+        <view class="list-wrap" v-else>
+            <view class="item" v-for="item in list" :key="item.BroadcastId" @click="handleGoDetail(item)">
+                <view class="title">{{item.BroadcastName}}</view>
+                <view class="time" v-if="item.PublishState==0">保存时间:{{item.ModifyTime|formatTime}}</view>
+                <view class="time" v-else>发布时间:{{item.PublishTime|formatTime}}</view>
+                <view class="flex audio-box" @click.stop="handlePlay(item)">
+                    <image 
+                        :src="item.BroadcastId==curVoiceId&&!curAudioPaused?require('@/static/voice/playing.png'):require('@/static/voice/pause.png')" 
+                        mode="widthFix" 
+                    />
+                    <text>{{item.VoicePlaySeconds|formatVoiceTime}}</text>
+                </view>
+                <view class="btns-box">
+                    <button 
+                        v-if="item.PublishState!=0"
+                        class="btn" 
+                        open-type="share" 
+                        :data-item="item"
+                        @click.stop=""
+                    >
+                        <image class="share-img" src="@/static/share-icon.png" mode="aspectFill"/>
+                    </button>
+                    <button class="btn" @click.stop="handleDelItem(item)">
+                        <image class="del-img" src="@/static/voice/del.png" mode="widthFix" v-if="item.IsAuthor" />
+                    </button>
+                    <button class="btn" @click.stop="handleSendMsgItem(item)" v-if="item.CouldSendMsg&&item.PublishState!=0">
+                        <image class="publish-img" src="@/static/voice/publish.png" mode="widthFix" />
+                    </button>
+                    <button class="btn" @click.stop="handlePublish(item)" v-if="item.PublishState==0">
+                        <image class="publish-img" src="./static/publish.png" mode="widthFix" />
+                    </button>
+                </view>
+            </view>
+        </view>
+
+
+        <!-- 音频悬浮 -->
+        <view v-if="showPage">
+        <audioBox v-if="showAudioPop"/>
+        </view>
+        
+        <van-dialog id="van-dialog" />
+    </view>
+</template>
+
+<script>
+import {apiVoiceList,apiVoicePlayRecord,apiVoiceDel,apiVoiceSendMsg,apiVoicePublish,apiMyVoiceCount} from '@/api/voice'
+import audioBox from '@/components/audioBox/audioBox.vue'
+const dayjs=require('@/utils/dayjs.min')
+export default {
+    components:{
+        audioBox
+    },
+    filters:{
+        formatTime(e){
+            return dayjs(e).format('YYYY-MM-DD HH:mm:ss')
+        },
+        formatVoiceTime(e){
+            let m=parseInt(e/60)
+            let s=parseInt(e%60)
+            return `${m>9?m:'0'+m}:${s>9?s:'0'+s}`
+        }
+    },
+    computed:{
+        showAudioPop(){//是否显示音频弹窗
+            return this.$store.state.audio.show
+        },
+        showAudioBigPop(){
+            return this.$store.state.audio.showBig
+        },
+        curVoiceId(){//当前正在播放的音频id
+            return this.$store.state.audio.voiceId
+        },
+        curAudioPaused(){//当前音频是否暂停状态
+            return this.$store.state.audio.paused
+        },
+    },
+    data() {
+        return {
+            page:1,
+            pageSize:20,
+            list:[],
+            finished:false,
+            status:0,//0-未发布 1-已发布 2-全部(我的语音播报列表)
+            statusOpt:[
+                {
+                    lable:'未发布',
+                    count:0,
+                    value:0,
+                    key:'Unpublished'
+                },
+                {
+                    lable:'已发布',
+                    count:0,
+                    value:1,
+                    key:'Published'
+                },
+                {
+                    lable:'全部',
+                    count:0,
+                    value:2,
+                    key:'All'
+                }
+            ],
+
+            showPage:false,
+        }
+    },
+    onLoad(){
+        this.getCount()
+        this.getList()
+        this.addListenVoiceSuccess()
+    },
+    onShow(){
+        this.showPage=true
+    },
+    onHide(){
+        this.showPage=false
+    },
+    onUnload(){
+		uni.$off('addVoiceSuccess')
+	},
+    onPullDownRefresh(){
+        this.page=1
+        this.list=[]
+        this.finished=false
+        this.getList()
+        setTimeout(() => {
+            uni.stopPullDownRefresh()
+        }, 1500)
+    },
+    onReachBottom() {
+        if(this.finished) return
+        this.page++
+        this.getList()
+    },
+    onShareAppMessage({from,target}) {
+        console.log(from,target);
+        let path='/pages/voice/voice'
+        let title='语音播报'
+        let imageUrl=''
+        if(from=='button'){
+            title=`${target.dataset.item.SectionName}:${target.dataset.item.BroadcastName}`
+            path=`/pages-voice/voiceDetail?voiceId=${target.dataset.item.BroadcastId}`
+            imageUrl=target.dataset.item.ImgUrl
+        }
+        return {
+            title:title,
+            path:path,
+            imageUrl:imageUrl
+        }
+    },
+    methods: {  
+        // 监听添加音频成功刷新列表
+        addListenVoiceSuccess(){
+            uni.$on('addVoiceSuccess',()=>{
+                this.page=1
+                this.list=[]
+                this.finished=false
+                this.getCount()
+                this.getList()
+            })
+        },
+
+        //获取列表数据
+        async getList(){
+            const res=await apiVoiceList({
+                page_index:this.page,
+                page_size:this.pageSize,
+                author_id:Number(this.$store.state.user.userInfo.user_id),
+                mine_status:this.status
+            })
+            if(res.code===200){
+                let arr=res.data.List||[]
+                this.list=[...this.list,...arr]
+                if(arr.length===0){
+                    this.finished=true
+                }
+            }
+        },
+
+        //获取数量
+        async getCount(){
+            const res=await apiMyVoiceCount({author_id:this.$store.state.user.userInfo.user_id})
+            if(res.code===200){
+                this.statusOpt.forEach(item => {
+                    item.count=res.data[item.key]
+                })
+            }
+        },
+
+        //状态改变
+        handleOptChange(item){
+            this.status=item.value
+            this.page=1
+            this.list=[]
+            this.finished=false
+            this.getList()
+        },
+
+        //跳转详情
+        handleGoDetail(item){
+            if(item.PublishState==0){//未发布跳转编辑
+                uni.navigateTo({
+                    url: '/pages-voice/addVoice?voiceId='+item.BroadcastId,
+                });
+            }else{
+                uni.navigateTo({
+                    url: '/pages-voice/voiceDetail?voiceId='+item.BroadcastId,
+                });
+            }
+            
+        },
+        
+        //推送消息
+        handleSendMsgItem(item){
+            this.$dialog.confirm({
+                title:'',
+                message: '该操作将推送模板消息和客群,确认推送吗?',
+                confirmButtonText:'确认'
+            }).then(()=>{
+                apiVoiceSendMsg({broadcast_id:item.BroadcastId}).then(res=>{
+                    if(res.code===200){
+                        uni.showToast({
+                            title:"推送成功",
+                            icon:'success'
+                        })
+                        item.CouldSendMsg=false
+                    }
+                })
+            }).catch(()=>{})
+        },
+
+        //删除音频
+        handleDelItem(item){
+            this.$dialog.confirm({
+                title:'',
+                message: '确定要删除该语音播报吗?',
+                confirmButtonText:'确定'
+            }).then(()=>{
+                if(this.curVoiceId==item.BroadcastId&&!this.curAudioPaused){
+                    //删除的音频正好在播放则暂停
+                    this.globalBgMusic.stop()
+                }
+                apiVoiceDel({broadcast_id:Number(item.BroadcastId)}).then(res=>{
+                    if(res.code===200){
+                        uni.showToast({
+                            title:'操作成功',
+                            icon:'none'
+                        })
+                        this.page=1
+                        this.list=[]
+                        this.finished=false
+                        this.getList()
+                        this.getCount()
+                    }
+                })
+            }).catch(()=>{})
+        },
+
+        //未发布时点击立即发布并且推送
+        handlePublish(item){
+            this.$dialog.confirm({
+                title:'',
+                message: '该操作将发布并且推送模板消息和客群',
+                confirmButtonText:'确认'
+            }).then(async ()=>{
+                const pubRes=await apiVoicePublish({
+                    broadcast_id:Number(item.BroadcastId),
+                    publish_type:1
+                })
+                if(pubRes.code===200){
+                    const sendRes=await apiVoiceSendMsg({broadcast_id:item.BroadcastId})
+                    if(sendRes.code===200){
+                        uni.showToast({
+                            title:'发布且推送成功',
+                            icon:'success'
+                        })
+                        setTimeout(() => {
+                            this.page=1
+                            this.list=[]
+                            this.finished=false
+                            this.getList()
+                            this.getCount()
+                        }, 1000);
+                    }else{
+                        uni.showToast({
+                            title:sendRes.msg,
+                            icon:'none'
+                        })
+                    }
+                }else{
+                    uni.showToast({
+                        title:pubRes.msg,
+                        icon:'none'
+                    })
+                }
+            }).catch(()=>{})
+        },
+
+        //点击音频 播放或者暂停
+        handlePlay(item){
+            if(this.$store.state.audio.voiceId==item.BroadcastId){
+                if(this.globalBgMusic.paused){
+                    this.globalBgMusic.play()
+                }else{
+                    this.globalBgMusic.pause()
+                }
+            }else{
+                const list=[{url:item.VoiceUrl,time:item.VoicePlaySeconds,title:item.BroadcastName,}]
+                this.$store.commit('audio/addAudio',{
+                    list:list,
+                    voiceId:item.BroadcastId
+                })
+                this.handleVoicePlayRecord(item)
+            }
+        },
+
+        //上报音频播放记录
+        async handleVoicePlayRecord(item){
+            const res=await apiVoicePlayRecord({
+                broadcast_id:item.BroadcastId
+            })
+            if(res.code===200){
+                console.log('上报音频播放记录');
+            }
+        }
+    },
+}
+</script>
+
+<style lang="scss" scoped>
+.empty-box{
+    text-align: center;
+    font-size: 32rpx;
+    color: #999;
+    padding-top: 150rpx;
+    image{
+        width: 600rpx;
+        margin-bottom: 57rpx;
+    }
+}
+.top-tab-warp{
+    padding: 34rpx;
+    position: sticky;
+    top: 0;
+    left: 0;
+    background: #ffffff;
+    z-index: 99;
+    .item{
+        box-sizing: border-box;
+        width: 200rpx;
+        text-align: center;
+        padding: 20rpx 10rpx;
+        background: #F5F5F5;
+        border-radius: 4rpx;
+        margin-right: 30rpx;
+        display: inline-block;
+        &:last-child{
+            margin-right: 0;
+        }
+    }
+    .item-active{
+        background: #FDF8F2;
+        color: #E3B377;
+    }
+}
+.list-wrap{
+    padding: 0 34rpx 34rpx 34rpx;
+    .item{
+        border-bottom: 1px solid #CDCDCD;
+        padding: 30rpx 0;
+        position: relative;
+
+        .btns-box{
+            position: absolute;
+            bottom: 34rpx;
+            right: 0;
+            
+            .btn{
+                float: right;
+                margin-left: 60rpx;
+                background-color: transparent;
+                line-height: 1;
+                padding: 0;
+                overflow: visible;
+                &::after{
+                    border: none;
+                }
+                .publish-img{
+                    width: 34rpx;
+                    height: 34rpx;
+                }
+                .del-img{
+                    width: 34rpx;
+                    height: 34rpx;
+                }
+                .share-img{
+                    width: 32.5rpx;
+                    height: 32rpx;
+                }
+                .clock-img{
+                    width: 32rpx;
+                    height: 33.5rpx;
+                }
+            }
+        }
+
+
+        .title{
+            font-size: 32rpx;
+        }
+        .time{
+            font-size: 28rpx;
+            color: #666;
+            margin-top: 20rpx;
+            margin-bottom: 30rpx;
+        }
+        .audio-box{
+            width: 185rpx;
+            height: 56rpx;
+            align-items: center;
+            justify-content: center;
+            border-radius: 28rpx;
+            background-color: #F4E1C9;
+            color: #E3B377;
+            image{
+                width: 23rpx;
+                height: 28rpx;
+                margin-right: 20rpx;
+            }
+        }
+    }
+}
+</style>

BIN
pages-voice/static/camera.png


BIN
pages-voice/static/clock-icon.png


BIN
pages-voice/static/publish.png


+ 317 - 0
pages-voice/voiceDetail.vue

@@ -0,0 +1,317 @@
+<template>
+    <view class="voice-detail" v-if="isAuth">
+        <!-- <view class="section-name">{{info.SectionName}}</view> -->
+        <view class="title">{{info.BroadcastName}}</view>
+        <view class="time">发布时间:{{info.PublishTime|formatTime}}</view>
+        <view class="flex audio-box">
+            <image 
+                :src="voiceId==curVoiceId&&!curAudioPaused?require('@/static/voice/playing.png'):require('@/static/voice/pause.png')" 
+                mode="aspectFill" 
+                @click="handlePlayAudio" 
+            />
+            <slider
+                activeColor="#E6B77D"
+                :max="duration" 
+                :value="curTime" 
+                @change="handleAudioSliderChange($event)"
+                block-size="12"
+                class="slider"
+            />
+            <text class="left-time">{{curTime|formatVoiceTime}}</text>
+            <text class="right-time">{{duration|formatVoiceTime}}</text>
+        </view>
+        <image class="del-btn" src="@/static/voice/del.png" mode="widthFix" @click="handleDel" v-if="info.IsAuthor"/>
+        <image class="publish-btn" src="@/static/voice/publish.png" mode="widthFix" @click="handleSendMsg" v-if="info.CouldSendMsg"/>
+
+        <!-- 图片部分 -->
+        <view class="imgs-box">
+            <image 
+                v-for="item in info.Imgs"
+                :key="item"
+                :src="item" 
+                mode="widthFix" 
+                lazy-load="false"
+                @click="preViewImg(item)"
+            />
+        </view>
+          
+        
+        <view v-show="false"><audioBox v-if="showAudioPop"/></view>
+
+        <van-dialog id="van-dialog" />
+
+        <!-- 跳转去提问悬浮按钮 -->
+        <dragButton :existTabBar="true">
+            <navigator url="/pages-question/hasQuestion">
+                <view class="to-question-fixed-box">
+                    <image src="@/static/toquestion-icon.png" mode="widthFix" />
+                    <text>我要提问</text>
+                </view>
+            </navigator>
+        </dragButton>
+
+    </view>
+    <noAuth :info="noAuthData" v-else/>
+
+</template>
+
+<script>
+import {apiVoicePlayRecord,apiVoiceDel,apiVoiceDetail,apiVoiceSendMsg} from '@/api/voice'
+import {apiGetSceneToParams} from '@/api/common'
+import noAuth from '@/pages/voice/components/noAuth.vue'
+import audioBox from '@/components/audioBox/audioBox.vue'
+import dragButton from '@/components/dragButton/dragButton.vue'
+const dayjs=require('@/utils/dayjs.min')
+export default {
+    components:{
+        noAuth,
+        audioBox,
+        audioBox
+    },
+    computed:{
+        showAudioPop(){//是否显示音频弹窗
+            return this.$store.state.audio.show
+        },
+        curVoiceId(){//当前正在播放的音频id
+            return this.$store.state.audio.voiceId
+        },
+        curAudioPaused(){//当前音频是否暂停状态
+            return this.$store.state.audio.paused
+        },
+        curTime(){
+            let t=0
+            if(this.voiceId==this.$store.state.audio.voiceId){
+                t=this.$store.state.audio.curTime
+            }
+            return t
+        }
+    },
+    filters:{
+        formatTime(e){
+            return dayjs(e).format('YYYY-MM-DD HH:mm:ss')
+        },
+        formatVoiceTime(e){
+            let m=parseInt(e/60)
+            let s=parseInt(e%60)
+            return `${m>9?m:'0'+m}:${s>9?s:'0'+s}`
+        }
+    },
+    data() {
+        return {
+            voiceId:'',
+            isAuth:true,
+            noAuthData:null,
+            info:{},
+
+            // curTime:0,
+            duration:0,
+        }
+    },
+    onLoad(options){
+        this.init(options)
+    },
+
+    onShareAppMessage(){
+        const title=`${this.info.SectionName}:${this.info.BroadcastName}`
+        return {
+            title:title,
+            imageUrl:this.info.ImgUrl
+        }
+    },
+
+    methods: {
+        async init(options){
+            if(options.scene){
+                const res=await apiGetSceneToParams({scene_key:options.scene})
+                if(res.code===200){
+                    const obj=JSON.parse(res.data)
+                    this.voiceId=obj.voiceId
+                }
+            }else{
+                this.voiceId=options.voiceId||0
+            }
+            this.getDetail()
+        },
+
+        async getDetail(){
+            const res=await apiVoiceDetail({broadcast_id:Number(this.voiceId)})
+            if(res.code===200){
+                this.info=res.data
+                uni.setNavigationBarTitle({ title: res.data.SectionName||'播报详情' })
+                this.duration=Number(res.data.VoicePlaySeconds)||0
+                this.isAuth=true
+            }else if(res.code===403){
+                this.isAuth=false
+                this.noAuthData=res.data
+            }
+        },
+
+        //删除
+        handleDel(){
+            this.$dialog.confirm({
+                title:'',
+                message: '确定要删除该语音播报吗?',
+                confirmButtonText:'确定'
+            }).then(()=>{
+                apiVoiceDel({broadcast_id:Number(this.voiceId)}).then(res=>{
+                    if(res.code===200){
+                        uni.showToast({
+                            title:'操作成功',
+                            icon:'none'
+                        })
+                        setTimeout(() => {
+                            uni.$emit('addVoiceSuccess')
+                            uni.switchTab({
+                                url: '/pages/voice/voice'
+                            });
+                        }, 1000);
+                    }
+                })
+            }).catch(()=>{})
+        },
+
+        // 推送消息
+        handleSendMsg(){
+            this.$dialog.confirm({
+                title:'',
+                message: '该操作将推送模板消息和客群,确认推送吗?',
+                confirmButtonText:'确认'
+            }).then(()=>{
+                apiVoiceSendMsg({broadcast_id:Number(this.voiceId)}).then(res=>{
+                    if(res.code===200){
+                        uni.showToast({
+                            title:'操作成功',
+                            icon:'none'
+                        })
+                        setTimeout(() => {
+                            uni.$emit('addVoiceSuccess')
+                            uni.switchTab({
+                                url: '/pages/voice/voice'
+                            });
+                        }, 1000);
+                    }
+                })
+            }).catch(()=>{})
+        },
+
+        //上报音频播放记录
+        async handleVoicePlayRecord(){
+            const res=await apiVoicePlayRecord({
+                broadcast_id:Number(this.voiceId)
+            })
+            if(res.code===200){
+                console.log('上报音频播放记录');
+            }
+        },
+
+        //点击播放\暂停音频
+        handlePlayAudio(){
+            if(this.$store.state.audio.voiceId==this.voiceId){
+                if(this.globalBgMusic.paused){
+                    this.globalBgMusic.play()
+                }else{
+                    this.globalBgMusic.pause()
+                }
+            }else{
+                const list=[{url:this.info.VoiceUrl,time:this.info.VoicePlaySeconds,title:this.info.BroadcastName,}]
+                this.$store.commit('audio/addAudio',{
+                    list:list,
+                    voiceId:this.voiceId
+                })
+                this.handleVoicePlayRecord()
+            }
+        },
+
+
+        //拖动音频播放进度条
+        handleAudioSliderChange(e){
+            const value=e.detail.value
+            this.globalBgMusic.seek(value)
+        },
+
+        //预览图片
+        preViewImg(item){
+            wx.previewImage({
+                current: item, // 当前显示图片的 http 链接
+                urls: this.info.Imgs||[] // 需要预览的图片 http 链接列表
+            })
+        }
+
+
+    },
+}
+</script>
+
+<style lang="scss" scoped>
+    .voice-detail{
+        padding: 50rpx 34rpx;
+        .section-name{
+            background: #FDF8F2;
+            border-radius: 8rpx;
+            border: 1px solid #E3B377;
+            display: inline-block;
+            padding: 19rpx 27rpx;
+            margin-bottom: 40rpx;
+        }
+        .title{
+            font-size: 32rpx;
+            line-height: 38rpx;
+            margin-bottom: 20rpx;
+        }
+        .time{
+            color: #999;
+            font-size: 28rpx;
+            line-height: 33rpx;
+        }
+        .audio-box{
+            background-color: #FDF8F2;
+            height: 123rpx;
+            align-items: center;
+            margin-top: 50rpx;
+            margin-bottom: 40rpx;
+            padding: 0 30rpx;
+            position: relative;
+            .left-time{
+                position: absolute;
+                bottom: 20rpx;
+                left: 100rpx;
+                color: #999999;
+                font-size: 20rpx;
+            }
+            .right-time{
+                position: absolute;
+                bottom: 20rpx;
+                right: 40rpx;
+                color: #999999;
+                font-size: 20rpx;
+            }
+            image{
+                width: 40rpx;
+                height: 48rpx;
+                flex-shrink: 0;
+                margin-right: 30rpx;
+            }
+            .slider{
+                flex: 1;
+                margin: 0 10rpx;
+            }
+        }
+        .del-btn{
+            float: left;
+            width: 36rpx;
+            height: 36rpx;
+        }
+        .publish-btn{
+            float: right;
+            width: 36rpx;
+            height: 36rpx;
+        }
+        .imgs-box{
+            margin-top: 120rpx;
+            image{
+                width: 100%;
+                margin-bottom: 40rpx;
+            }
+        }
+    }
+</style>

+ 43 - 3
pages.json

@@ -238,6 +238,13 @@
 				//ficc介绍页面
 				{
 					"path": "ficcService"
+				},
+				//预览图片
+				{
+					"path":"previewImage",
+					"style":{
+						"navigationStyle": "custom"
+					}
 				}
 			]
 		},
@@ -288,6 +295,38 @@
 					"style":{
 						"navigationBarTitleText": "新建语音"
 					}
+				},
+				{
+					"path": "voiceDetail",
+					"style":{
+						"navigationBarTitleText": "播报详情"
+					}
+				},
+				{
+					"path": "myVoice",
+					"style":{
+						"navigationBarTitleText": "我的语音",
+						"enablePullDownRefresh": true
+					}
+				}
+			]
+		},
+		//线上路演模块
+		{
+			"root":"pages-roadShow",
+			"pages":[
+				{
+					"path":"video/list",
+					"style": {
+						"navigationBarTitleText": "线上路演",
+						"enablePullDownRefresh": true
+					}
+				},
+				{
+					"path":"video/search",
+					"style": {
+						"navigationBarTitleText": "线上路演"
+					}
 				}
 			]
 		}
@@ -354,15 +393,16 @@
 			"van-checkbox": "/wxcomponents/vant/checkbox/index",
 			"van-transition": "/wxcomponents/vant/transition/index",
 			"van-collapse": "/wxcomponents/vant/collapse/index",
-  		"van-collapse-item": "/wxcomponents/vant/collapse-item/index",
-		"van-action-sheet": "/wxcomponents/vant/action-sheet/index",
+			"van-collapse-item": "/wxcomponents/vant/collapse-item/index",
+			"van-action-sheet": "/wxcomponents/vant/action-sheet/index",
 			"van-tag": "/wxcomponents/vant/tag/index",
 			"van-row": "/wxcomponents/vant/row/index",
   			"van-col": "/wxcomponents/vant/col/index",
 			"van-progress": "/wxcomponents/vant/progress/index",
 			"van-dialog": "/wxcomponents/vant/dialog/index",
 			"van-cell": "/wxcomponents/vant/cell/index",
-			"van-tree-select": "/wxcomponents/vant/tree-select/index"
+			"van-tree-select": "/wxcomponents/vant/tree-select/index",
+			"van-datetime-picker": "/wxcomponents/vant/datetime-picker/index"
 		}
 	}
 }

+ 49 - 6
pages/activity/activity.vue

@@ -205,7 +205,9 @@ import {
     apiActivityCancelRemind, 
     apiActivityRegister,
     apiActivityCancelRegister,
-    apiActivityAudios
+    apiActivityAudios,
+    apiActivityAudioPlayRecordAdd,
+    apiActivityAudioPlayRecordUpate
 } from '@/api/activity'
 import {apiApplyPermission,apiUserInfo} from '@/api/user'
 import sharePoster from '../../components/sharePoster/sharePoster.vue'
@@ -309,6 +311,7 @@ export default {
                 audioCurrentTime:0,//音频播放实时时间
                 audioTime:0,//当前音频时间
                 audioCurrentUrl:'',//当前音频地址
+                recordId:0,//新增音频播放记录成功的id
             },
 
             showPoster:true
@@ -377,6 +380,31 @@ export default {
             const endTime=dayjs(end).format('HH:mm');
             return `${day} ${startTime}-${endTime} ${week}`
         },
+
+        //新增音频播放统计
+        async handleAudioPlayRecordAdd(primary_id,extend_id){
+            const res=await apiActivityAudioPlayRecordAdd({
+                primary_id:Number(primary_id),
+                extend_id:Number(extend_id),
+                from_page:'活动列表'
+            })
+            if(res.code===200){
+                console.log('新增音频播放记录成功');
+                this.currentAudioMsg.recordId=res.data.id
+            }
+        },
+
+        //更新音频播放记录
+        async handleAudioPlayRecordUpdate(){
+            const res=await apiActivityAudioPlayRecordUpate({
+                id:Number(this.currentAudioMsg.recordId),
+                stop_seconds:Number(this.currentAudioMsg.audioCurrentTime)
+            })
+            if(res.code===200){
+                console.log('更新音频播放记录成功');
+            }
+        },
+
         // 初始化音频状态
         initAudio(){
             console.log('音频src',this.globalBgMusic.src);
@@ -396,17 +424,23 @@ export default {
                     audioCurrentTime:0,//音频播放实时时间
                     audioTime:0,//当前音频时间
                     audioCurrentUrl:'',//当前音频地址
+                    recordId:0
                 }
             }
         },
 
         // 点击列表中播放音频
         async handleGetAudio(item){
+            await this.checkUserIsBind()
             if(!(item.firstActivityTypeId===1&&item.activityState===3&&item.hasPlayBack)) return
             // 获取音频
             if(this.currentAudioMsg.activityId!=item.activityId){
                 const res=await apiActivityAudios({activity_id: Number(item.activityId)})
                 if(res.code===200){
+                    // 如果当前有其他的在播放则要掉一次更新
+                    if(this.currentAudioMsg.activityId){
+                        this.handleAudioPlayRecordUpdate()
+                    }
                     if(res.data){
                         this.currentAudioMsg.activityId=item.activityId
                         this.currentAudioMsg.list=res.data
@@ -458,6 +492,7 @@ export default {
 
         // 播放音频
         handlePlayAudio(item){
+            this.handleAudioPlayRecordAdd(item.activity_voice_id,item.activityId)
             this.globalBgMusic.src=item.voiceUrl
             this.globalBgMusic.title=item.voiceName
 
@@ -479,14 +514,16 @@ export default {
                 this.currentAudioMsg.play=false
             })
             this.globalBgMusic.onStop(()=>{
-                this.currentAudioMsg.play=false
+                console.log('stop',this.currentAudioMsg);
+                this.handleAudioPlayRecordUpdate()
                 this.currentAudioMsg.play=false
                 this.currentAudioMsg.audioCurrentTime=0
                 this.currentAudioMsg.audioTime=0
                 this.currentAudioMsg.audioCurrentUrl=0
             })
             this.globalBgMusic.onEnded(()=>{
-                console.log('onEnded');
+                console.log('onEnded',this.currentAudioMsg);
+                this.handleAudioPlayRecordUpdate()
                 this.currentAudioMsg.play=false
                 this.handleAudioBtn('next','auto')
             })
@@ -517,6 +554,7 @@ export default {
             }
             if(type==='before'){
                 if(!this.isFirstAudio){
+                    this.handleAudioPlayRecordUpdate()
                     this.currentAudioMsg.list.forEach((_item,index)=>{
                         if(_item.voiceUrl==this.currentAudioMsg.audioCurrentUrl){
                             this.handlePlayAudio(this.currentAudioMsg.list[index-1])
@@ -527,6 +565,7 @@ export default {
 
             if(type==='next'){
                 if(!this.isLastAudio){
+                    this.handleAudioPlayRecordUpdate()
                     this.currentAudioMsg.list.forEach((_item,index)=>{
                         if(_item.voiceUrl==this.currentAudioMsg.audioCurrentUrl){
                             this.handlePlayAudio(this.currentAudioMsg.list[index+1])
@@ -667,7 +706,8 @@ export default {
             this.getList()
         },
 
-        handleGoDetail(id) {
+        async handleGoDetail(id) {
+            await this.checkUserIsBind()
             uni.navigateTo({
                 url: '/pages-activity/detail?id=' + id
             });
@@ -694,7 +734,8 @@ export default {
             }
         },
 
-        handleRemind(e,index){    
+        async handleRemind(e,index){   
+            await this.checkUserIsBind()
             if(e.hasRemind===0){
                 this.handleAddRemind(e,index)
             }else{
@@ -770,7 +811,8 @@ export default {
             }
         },
 
-        handleRegister(e,index){
+        async handleRegister(e,index){
+            await this.checkUserIsBind()
             if(e.registerState===0){
                 this.handleAddRegister(e,index)
             }else{
@@ -850,6 +892,7 @@ export default {
 
         // 点击立即申请
         async handleApply(){
+            await this.checkUserIsBind()
             if(this.pupData.customer_info.has_apply){//已经申请过
                 this.pupData.content=`<p>您已提交过申请,请耐心等待</p>`
                 this.pupData.type=''

+ 2 - 1
pages/chart/component/noAuth.vue

@@ -83,7 +83,8 @@ export default {
             }
         },
 
-        handleApply(){
+        async handleApply(){
+            await this.checkUserIsBind()
             if(this.info.customer_info.status=='流失'){
                 apiApplyPermission({
                     company_name:this.info.customer_info.company_name,

+ 3 - 1
pages/pc.vue

@@ -19,7 +19,9 @@ const mapObj=new Map([
     ['pages-report/specialColumn/detail','/report/specialcolumndetail'],
     ['pages/video/videoList','/video/list'],
 	['pages-sandTable/sandTable','/sandBox/list'],
-    ['pages/voice/voice','/voice/list']
+    ['pages/voice/voice','/voice/list'],
+    ['pages-voice/voiceDetail','/voice/detail'],
+    ['pages-roadShow/video/list','/roadshow/video/list']
 ])//map映射小程序页面路径对应h5页面路径
 import {apiUserInfo} from '@/api/user'
 import {apiGetSceneToParams} from '@/api/common'

+ 1 - 1
pages/pricedriven/pricedriven.vue

@@ -1,5 +1,5 @@
 <template>
-    <web-view :src="url" @message="handleGetMessage"/></web-view>
+    <web-view :src="url" @message="handleGetMessage"></web-view>
 </template>
 
 <script>

+ 137 - 47
pages/question/question.vue

@@ -35,9 +35,8 @@
 				<image :src="globalImgUrls.activityNoAuth" mode="widthFix"  style="width:100%;"/>
 				<view>暂无提问<text v-if="userInfo.is_inner!==1">,快试试提问功能吧</text></view>
 			</view>
-			<view class="question-list" :class="{'last':finished}">
+			<view class="question-list" :class="showAudioPop?showAudioBigPop?'list-bot3':'list-bot2':'list-bot1'">
 				<view class="question-item" v-for="item in questionList" :key="item.community_question_id">
-					
 					<text class="item-time">提问时间:{{ item.create_time }}</text>
 					<view class="question-info">
 						<view style="flex:1;" class="question-title">
@@ -45,7 +44,13 @@
 							<!-- <text class="item-title"> -->{{ item.question_content }}<!-- </text> -->
 						</view>	
 						<view class="item-answer">
-							<view class="answer" @click="handleAudio(item)">
+							<!-- 改为背景音频播放 -->
+							<view class="answer" @click.stop="handlePlayAudioByBg(item)">
+								<image class="music-img" :src="item.community_question_id==curVoiceId&&!curAudioPaused?playImgSrc:pauseImgSrc" mode="widthFix"/>
+								<text>{{ dayjs(item.answer.audioTime).format('mm:ss') }}</text>
+							</view>
+							  
+							<!-- <view class="answer" @click.stop="handleAudio(item)">
 								<template v-if="!item.loading">
 									<image class="music-img" :src="item.answer.isplay?playImgSrc:pauseImgSrc" mode="widthFix"/>
 									<template v-if="item.answer.isplay || item.answer.ispause">
@@ -63,7 +68,7 @@
 									<image class="load-img" src="../../static/loading.png" mode="aspectFill" />
 									<text>{{ dayjs(item.answer.audioTime).format('mm:ss') }}</text>
 								</template>
-							</view>
+							</view> -->
 						</view>
 					</view>
 					
@@ -72,7 +77,7 @@
 				</view>
 			</view>
 			<template v-if="isUserResearcher">
-				<view class="btn-wrap">
+				<view class="btn-wrap" :class="showAudioPop?showAudioBigPop?'btn-bot3':'btn-bot2':'btn-bot1'">
 					<view class="btn topage-button" @click="toPage('question')" v-if="userInfo.status&&userAuth">
 						<image 
 							src="../../static/question/askquestion.png"
@@ -87,7 +92,7 @@
 				</view>
 			</template>
 			<template v-else>
-				<view class="topage-btn topage-button" @click="toPage('question')" v-if="userInfo.status&&userAuth">
+				<view class="topage-btn topage-button" :class="showAudioPop?showAudioBigPop?'btn-bot3':'btn-bot2':'btn-bot1'" @click="toPage('question')" v-if="userInfo.status&&userAuth">
 					<image 
 						src="../../static/question/askquestion.png"
 						mode="scaleToFill"
@@ -133,11 +138,16 @@
 				</view>
 			</view>
 		</van-popup>
+
+		<!-- 音频悬浮 -->
+        <view v-if="showPage">
+            <audioBox v-if="showAudioPop"/>
+        </view>
 		
 		<!-- 评论弹窗 -->
 		<van-action-sheet 
 			:show="isShowComment" 
-			title="全部评论"
+			title="问答评论"
 			@close="closeCommentHandle"
 			@clickOverlay="isShowComment=false"
 		>
@@ -145,15 +155,18 @@
 			   <view class="commment-top">
 				   <text class="title">{{comment_obj.title}} {{comment_obj.total}}</text>
 				   <view class="comment-top-right">
-					   <text :class="select_comment_type === 1 && 'act'" @click="changeCommentTypeHandle(1)">精选</text>
+					   <text :class="select_comment_type === 1 && 'act'" @click="changeCommentTypeHandle(1)">全部</text>
 					   <text :class="select_comment_type === 2 && 'act'" @click="changeCommentTypeHandle(2)">我的</text>
 				   </view>
 			   </view>
 				<scroll-view class="comment-list-cont" v-if="comment_obj.total" scroll-y>
                <view class="comment-list-item" v-for="(item,index) in commentList" :key="item.community_question_comment_id">
-                  <view class="comment">
-                     <text style="color: #333;">{{item.user_name}}:</text>
-                     {{item.content}}
+                  <view class="flex comment">
+						<image class="avatar" :src="item.qa_avatar_url" mode="aspectFill" lazy-load="true"/>
+						<view style="display:flex;align-items:center">{{item.content}}</view>
+						  
+                     <!-- <text style="color: #333;">{{item.user_name}}:</text>
+                     {{item.content}} -->
                   </view>
                   <view class="del" v-if="select_comment_type === 2"  @click="delCommentHandle(item,index)">删除该评论</view>
                </view>
@@ -171,8 +184,8 @@
                   :show-confirm-bar="false"
                   :cursor-spacing="20"
                   class="write-ipt"
-                  @focus="checkNickHandle"
                   @blur="setWritePosition(50)"
+					@focus="checkNickHandle"
                />
 				  <view class="confirm-btn" @click="publishMessageHandle">发布</view>
 				</view>
@@ -193,11 +206,17 @@ import {
    apiCanelNickTip,
    apiDelComment,
    apiPublishComment,
+   apiCountAudioClick
 } from '@/api/question'
 import questionComment from '@/components/questionComment/questionComment.vue'
+import audioBox from '@/components/audioBox/audioBox.vue'
 import { debounce } from '@/utils/common.js';
 export default {
 	mixins: [mixin],
+	components:{
+        audioBox,
+		questionComment
+    },
 	data() {
 		return {
 			questionList: [],
@@ -217,15 +236,17 @@ export default {
 				//seal_name:'梁娜',
 				//seal_mobile:123456,
 			},//mock用户信息 */
+			showPage:false,
+
 			waitNum:0,
 			
 			isShowComment: false,
 			comment_cont: '',
 			writeBottom: 50,
-			select_comment_type:1,//默认精选
+			select_comment_type:1,//默认全部
 			select_question_item: {},
 			comment_obj: {
-				title: '精选评论',
+				title: '全部评论',
 				total: 0
 			},
 			comment_total: 0,
@@ -237,8 +258,19 @@ export default {
 			this.getQuestionList(3)
 		}
 	},
-	components:{
-		questionComment
+	computed:{
+		showAudioPop(){//是否显示音频弹窗
+            return this.$store.state.audio.show
+        },
+        showAudioBigPop(){
+            return this.$store.state.audio.showBig
+        },
+        curVoiceId(){//当前正在播放的音频id
+            return this.$store.state.audio.questionId
+        },
+        curAudioPaused(){//当前音频是否暂停状态
+            return this.$store.state.audio.paused
+        },
 	},
 	onLoad() {
 		/* this.getVistor()
@@ -249,7 +281,11 @@ export default {
 		this.getdistributeNum()
 		this.getOptionList()
 		this.getQuestionList(3)
+		this.showPage=true
 	},
+	onHide(){
+        this.showPage=false
+    },
 	onReachBottom() {
 		if(this.finished) return
 		this.page++
@@ -262,6 +298,13 @@ export default {
 			uni.stopPullDownRefresh()
 		}, 1500);
 	},
+	//转发分享
+	onShareAppMessage(){
+		return{
+			title:'问答社区',
+			path:'/pages/question/question'
+		}
+	},
 	methods: {
 		//获取研究员问答列表数量统计
 		getdistributeNum() {
@@ -321,20 +364,17 @@ export default {
 				uni.navigateTo({ url: '/pages-question/answerList' })
 			}
 		},
-		//转发分享
-		onShareAppMessage(){
-			return{
-				title:'问答社区',
-				path:'/pages/question/question'
-			}
-		},
 		refreshPage() {
 			this.page = 1
 			this.selectId = -1
 			this.questionList = []
 			this.finished = false
 			this.getQuestionList(3)
-      },
+        },
+		//跳转详情
+		handleGoDetail(item){
+			uni.navigateTo({ url: `/pages-question/answerDetail?id=${item.community_question_id}` })
+		},
 		
 		/* 打开评论弹窗 */  
 		showCommentHandle(item) {
@@ -348,7 +388,7 @@ export default {
          this.isShowComment = false;
          this.select_question_item = {};
          this.comment_cont = '';
-         this.select_comment_type = 1;//默认精选
+         this.select_comment_type = 1;//默认全部
       },
       
       /* 切换评论类型*/
@@ -371,7 +411,7 @@ export default {
          if(code !== 200) return
          this.commentList = data.list || [];
          this.comment_obj = {
-            title: this.select_comment_type === 1 ? '精选评论' : '我的评论',
+            title: this.select_comment_type === 1 ? '全部评论' : '我的评论',
             total: data.paging.totals
          }
       },
@@ -389,8 +429,10 @@ export default {
                if( code !== 200 ) return
                
                this.commentList.splice(index,1)
+			   this.comment_obj.total--
                         
                wx.showToast({title: '删除成功'});
+			   this.refreshPage()
             }
          })
       },
@@ -399,27 +441,32 @@ export default {
          console.log(val)
          this.writeBottom = val < 50 ? 50 : val;
       },
+	  
       
       /* 校验昵称状态*/
+	  /**
+	   * 此方法不在调用(但是还是留着吧 万一哪天又要用呢)
+	   * 改版不用再校验是否设置过昵称,老板说要和报告不一样并且要好玩,所以改成随机生成的头像昵称(当然是后端去生成) 
+	   */
       async checkNickHandle(e) {
          this.setWritePosition(e.detail.height);
          
-         const { data,code } = await apiCheckNick();
+        //  const { data,code } = await apiCheckNick();
          
-         if(code !== 200) return
+        //  if(code !== 200) return
          
-         !data && wx.showModal({
-            title: '',
-            content: '检测到您还未设置头像和昵称,您的留言将发布为匿名,是否立即去设置?',
-            confirmText: '去设置',
-            confirmColor: '#E3B377',
-            cancelText	: '暂时不用',
-            cancelColor: '#666',
-            success: (res)=> {
-               res.confirm && wx.navigateTo({ url:'/pages-user/mysetting' });
-               res.cancel && this.setCancelNickHandle();
-            }
-         })
+        //  !data && wx.showModal({
+        //     title: '',
+        //     content: '检测到您还未设置头像和昵称,您的留言将发布为匿名,是否立即去设置?',
+        //     confirmText: '去设置',
+        //     confirmColor: '#E3B377',
+        //     cancelText	: '暂时不用',
+        //     cancelColor: '#666',
+        //     success: (res)=> {
+        //        res.confirm && wx.navigateTo({ url:'/pages-user/mysetting' });
+        //        res.cancel && this.setCancelNickHandle();
+        //     }
+        //  })
       },
       
       /* 不在提醒弹窗 */
@@ -439,8 +486,10 @@ export default {
          if(code !== 200) return
          wx.showToast({title: '发布成功'});
          this.comment_cont = '';
-         this.select_comment_type === 2 && this.getComment();
-      })
+        //  this.select_comment_type === 2 && this.getComment();
+		this.getComment()
+		this.refreshPage()//刷新列表
+      }),
 	}
 }
 </script>
@@ -558,6 +607,15 @@ page {
 			padding-bottom: 260rpx;
 		}
 	}
+	.list-bot1{
+		padding-bottom: 200rpx;
+	}
+	.list-bot2{
+		padding-bottom: 340rpx;
+	}
+	.list-bot3{
+		padding-bottom: 440rpx;
+	}
 
 	.topage-btn {
 		position: fixed;
@@ -589,7 +647,8 @@ page {
 		margin-left: -30rpx;
 		display: flex;
 		justify-content: center;
-		bottom: 215rpx;
+		bottom: calc(120rpx + constant(safe-area-inset-bottom));
+		bottom: calc(120rpx + env(safe-area-inset-bottom));
 		.btn{
 			width:300rpx;
 			height:80rpx;
@@ -598,6 +657,20 @@ page {
 			}
 		}
 	}
+
+	.btn-bot1{
+		bottom: calc(120rpx + constant(safe-area-inset-bottom));
+		bottom: calc(120rpx + env(safe-area-inset-bottom));
+	}
+	.btn-bot2{
+		bottom: calc(260rpx + constant(safe-area-inset-bottom));
+		bottom: calc(260rpx + env(safe-area-inset-bottom));
+	}
+	.btn-bot3{
+		bottom: calc(355rpx + constant(safe-area-inset-bottom));
+		bottom: calc(355rpx + env(safe-area-inset-bottom));
+	}
+
 	.global-pup{
 		.content{
 			padding:90rpx 34rpx;
@@ -662,10 +735,27 @@ page {
       position: relative;
       padding: 30rpx 0;
       .comment-list-item {
-				margin-bottom: 30rpx;
-				padding: 0 24rpx;
-				.comment { color: #666; }
-				.del { color: #D80000;margin-top: 10rpx;width: 200rpx; }
+			margin-bottom: 30rpx;
+			padding: 0 24rpx;
+			.comment { 
+				color: #666; 
+				.avatar{
+					width: 56rpx;
+					height: 56rpx;
+					border-radius: 50%;
+					flex-shrink: 0;
+					margin-right: 20rpx;
+				}
+				view{
+					flex: 1;
+				}
+			}
+			.del { 
+				color: #D80000;
+				margin-top: 10rpx;
+				margin-left: 76rpx;
+				width: 200rpx; 
+			}
       }
    }
    .write-wrap {

+ 43 - 40
pages/report/report.vue

@@ -12,18 +12,18 @@
       </view>
     </view>
 	
-	<!-- card -->
-	<view class="tab-card">
-		<view 
-			class="card-item" 
-			v-for="(tab,index) in tabCards" 
-			:key="index" 
-			@click="linkPage(tab)"
-		>
-			<image :src="tab.icon" mode="aspectFill" class="card-ico"/>
-			<view class="title">{{tab.tab}}</view>
-		</view>
-	</view>
+    <!-- card -->
+    <view class="tab-card">
+      <view 
+        class="card-item" 
+        v-for="(tab,index) in tabCards" 
+        :key="index" 
+        @click="linkPage(tab)"
+      >
+        <image :src="tab.icon+'?t='+new Date().getDay()" mode="aspectFill" class="card-ico"/>
+        <view class="title">{{tab.tab}}</view>
+      </view>
+    </view>
 	
     <!-- 分类 -->
     <view class="type-wrap">
@@ -117,25 +117,25 @@ export default {
       pageSize:20,
       finished:false,
 	  
-		tabPathMap: new Map([
-			['report','/pages-report/classify'],
-			['chart','/pages/chart/chart'],
-			['buy','/pages/buy/buy'],
-			['sandbox','/pages-sandTable/sandTable'],
-      ['activity','/pages/activity/activity']
-		]),
-		tabCards: []
+      tabPathMap: new Map([
+        ['report','/pages-report/classify'],
+        ['chart','/pages/chart/chart'],
+        ['road','/pages-roadShow/video/list'],
+        ['sandbox','/pages-sandTable/sandTable'],
+        ['activity','/pages/activity/activity']
+      ]),
+      tabCards: []
     }
   },
   onLoad(){ 
     this.initNavBar()
     this.getTopAuthList()
-	 this.getTopTab();
+	  this.getTopTab();
   },
   onShow() {
     uni.getSystemInfo({
 			success: function (res) {
-				if (res.windowWidth > 600||['windows','mac'].includes(res.platform)) {
+				if (res.windowWidth > 750||['windows','mac'].includes(res.platform)) {
 					console.log('跳转启动页判断进入pc');
 					uni.reLaunch({
 						url: "/pages/pc",
@@ -262,17 +262,17 @@ export default {
       }
     },
 
-	//跳转
-	linkPage({mark}) {
-		const url = this.tabPathMap.get(mark);
-		uni.navigateTo({ url,
-			fail () {
-			   uni.switchTab({
-				  url,
-			   })
-		   } 
-		})
-	},
+    //跳转
+    linkPage({mark}) {
+      const url = this.tabPathMap.get(mark);
+      uni.navigateTo({ url,
+        fail () {
+          uni.switchTab({
+            url,
+          })
+        } 
+      })
+    },
 	
     // 跳转分类
     goClassify(){
@@ -285,8 +285,9 @@ export default {
     },
     
     //跳转我的
-    goUser(){
-       uni.navigateTo({
+    async goUser(){
+      await this.checkUserIsBind()
+      uni.navigateTo({
         url: '/pages/user/user',
         fail () {
           uni.switchTab({
@@ -297,7 +298,7 @@ export default {
     },
 
     //跳转报告详情
-    goDetail(item){
+    async goDetail(item){
       if(['晨报','周报'].includes(item.classify_name_first)){
         uni.navigateTo({url: `/pages-report/chapterDetail?chapterId=${item.report_chapter_id}&fromPage=home`})
       }else{
@@ -407,14 +408,14 @@ movable-area{
   .first-type-box{
     justify-content: space-between;
     .item{
-		 min-width: 120rpx;
+		 min-width: 150rpx;
       flex-shrink: 0;
       text-align: center;
 		padding: 16rpx 0;
 		text-align: center;
 		background-color: #F5F5F5;
 		border-radius: 8rpx;
-      font-size: $global-font-size-sm;
+      // font-size: $global-font-size-sm;
       image{
         width: 100rpx;
         height: 100rpx;
@@ -551,9 +552,11 @@ movable-area{
 		// margin-right: 40rpx;
 		// &:last-child { margin-right: 0; }
 		.card-ico {
-			width: 100rpx;
-			height: 100rpx;
+			width: 60rpx;
+			height: 60rpx;
 			display: block;
+      margin-left: auto;
+      margin-right: auto;
 		}
 		.title {
 			text-align: center;

+ 13 - 0
pages/user/user.vue

@@ -71,6 +71,19 @@
 					<van-icon name="arrow"></van-icon>
 				</view>
 			</view>
+
+			<navigator url="/pages-voice/myVoice">
+				<view class="flex item-card">
+					<image src="../../static/voice/mine-voice-icon.png" mode="widthFix" />
+					<text class="label">我的语音</text>
+					<view class="right-text look">
+						<text>查看</text>
+						<van-icon name="arrow"></van-icon>
+					</view>
+				</view>
+				<view></view>
+			</navigator>
+
 			<view class="flex item-card" v-if="userInfo.status!='试用'">
 				<image src="../../static/calendar.png" mode="widthFix" />
 				<text class="label">服务截止日期</text>

+ 99 - 0
pages/video/components/noAuth.vue

@@ -0,0 +1,99 @@
+<template>
+  <view class="voice-no-auth">
+        <image class="img" :src="globalImgUrls.activityNoAuth" mode="widthFix"></image>
+		<view style="margin-bottom:15px">您暂无权限查看视频社区</view>
+		<view v-if="info.type==='contact'" style="margin-bottom:15px">若想查看可以联系对口销售</view>
+		<view v-else style="margin-bottom:15px">若想查看可以申请开通</view>
+		<view v-if="info.type==='contact'">
+			{{info.name||''}}:<text @click="handleCall" style="color:#E3B377">{{info.mobile||''}}</text>
+		</view>
+		<view class="global-btn-yellow-change btn" @click="handleApply" v-else style="margin-top:30px">立即申请</view>
+  </view>
+</template>
+
+<script>
+import {apiApplyPermission} from '@/api/user'
+export default {    
+    props: {
+        info:null
+    },
+    watch:{
+        info(){
+            this.handleAutoApply()
+        }
+    },
+    
+    methods: {
+        handleCall(){
+
+            uni.makePhoneCall({
+                phoneNumber: this.info.mobile,
+                success: (result) => {},
+                fail: (error) => {}
+            })
+        },
+
+        handleAutoApply(){
+            if(this.info.type=='contact'&&!this.info.customer_info.has_apply){
+                if(this.info.customer_info.status=='冻结'||(this.info.customer_info.status=='试用'&&this.info.customer_info.is_suspend==1)){
+                    apiApplyPermission({
+                        company_name:this.info.customer_info.company_name,
+                        real_name:this.info.customer_info.name,
+                        source:9,
+                        from_page:'视频社区'
+                    }).then(res=>{
+                        if(res.code===200){
+                            console.log('主动申请成功');
+                        }
+                    }) 
+                }
+            }
+        },
+
+        async handleApply(){
+            await this.checkUserIsBind()
+            const {customer_info}=this.info
+            if(customer_info.has_apply){
+                uni.showToast({
+                  title:'您已提交过申请,请耐心等待',
+                  icon:'none'
+                })
+            }else{
+                if (!customer_info.status || customer_info.status != '流失') {
+                    uni.navigateTo({
+                        url: "/pages-applyPermission/applyPermission?source=9&from_page=视频社区"
+                    })
+                }else{
+                    apiApplyPermission({
+                        company_name:customer_info.company_name,
+                        real_name:customer_info.name,
+                        source:9,
+                        from_page:'视频社区'
+                    }).then(res=>{
+                        uni.navigateTo({url:'/pages-applyPermission/applyResult'})
+                    })
+                }
+            }
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.voice-no-auth{
+    padding: 34rpx;
+    text-align: center;
+    font-size: $global-font-size-lg;
+    .img{
+        width: 100%;
+        margin-bottom: 50rpx;
+    }
+    .btn{
+        width: 380rpx;
+        line-height: 70rpx;
+        margin-left: auto;
+        margin-right: auto;
+        margin-top: 40rpx;
+    }
+}
+</style>

+ 40 - 6
pages/video/videoList.vue

@@ -1,5 +1,5 @@
 <template>
-    <view class="video-list-page">
+    <view class="video-list-page" v-if="isAuth">
         <van-sticky style="background: #fff">
             <view class="flex search-wrap">
                 <view @click="goSearchPage" style="flex:1;margin-right:30rpx" >
@@ -20,7 +20,11 @@
                 :src="globalImgUrls.activityNoAuth"
                 mode="widthFix"
             />
-            <view>暂无数据</view>
+            <view v-if="videoId">
+                <view>该视频不存在</view>
+                <view>请刷新页面后重试</view>
+            </view>
+            <view v-else>暂无数据</view>
         </view>
         <view class="list-wrap">
             <view class="item" v-for="item in list" :key="item.community_video_id">
@@ -58,7 +62,7 @@
             round 
             @close="showFilter=false"
         >
-            <view class="filter-wrap">
+            <view class="filter-wrap" @touchmove.stop>
                 <view class="flex top">
                     <text style="color:#000">全部筛选</text>
                     <text style="color:#E3B377" @click="showFilter=false">取消</text>
@@ -89,14 +93,30 @@
                 </view>
             </view>
         </van-popup>
+
+        <!-- 跳转去提问悬浮按钮 -->
+        <dragButton :existTabBar="true">
+            <navigator url="/pages-question/hasQuestion">
+                <view class="to-question-fixed-box">
+                    <image src="@/static/toquestion-icon.png" mode="widthFix" />
+                    <text>我要提问</text>
+                </view>
+            </navigator>
+        </dragButton>
     </view>
-    
+    <noAuth :info="noAuthData" v-else/>
 </template>
 <script>
 import {apiVideoList,apiVideoPlayLog} from '@/api/video'
 import {apiOptionList} from '@/api/question'
 import {apiGetSceneToParams,apiGetTagTree} from '@/api/common'
+import noAuth from './components/noAuth.vue'
+import dragButton from '@/components/dragButton/dragButton.vue'
 export default {
+    components:{
+        noAuth,
+        dragButton
+    },
     data() {
         return {
             showFilter:false,
@@ -111,16 +131,25 @@ export default {
             list:[],
 
             curVideoId:0,
-            curVideoIns:null
+            curVideoIns:null,
+
+            isAuth:true,
+            noAuthData:null,
         }
     },
     onLoad(options){
         this.init(options)
         this.getPermissionList()
     },
+    onShow(){
+        //无权限时刷新列表
+        if(!this.isAuth){
+            this.getList()
+        }
+    },
     onHide(){
         this.showFilter=false
-        this.curVideoId=0
+        // this.curVideoId=0
     },
     onShareAppMessage({from,target}) {
         console.log(from,target);
@@ -208,6 +237,11 @@ export default {
                 if(arr.length===0){
                     this.finished=true
                 }
+                this.isAuth=true
+            }else if(res.code===403){
+                //无权限用户
+                this.isAuth=false
+                this.noAuthData=res.data
             }
         },
 

+ 2 - 1
pages/voice/components/noAuth.vue

@@ -50,7 +50,8 @@ export default {
             }
         },
 
-        handleApply(){
+        async handleApply(){
+            await this.checkUserIsBind()
             const {customer_info}=this.info
             if(customer_info.has_apply){
                 uni.showToast({

+ 192 - 95
pages/voice/voice.vue

@@ -1,10 +1,29 @@
 <template>
     <view class="voice-play-page" v-if="isAuth">
-        <view class="top-filter-box">
+        <!-- 由于目前板块较少 先去掉底部弹出筛选 改为平铺展示在tab上 -->
+        <!-- <view class="top-filter-box">
             <image src="@/static/question/select.png" mode="aspectFill" @click="showFilter = true" />
             <text @click="showFilter = true">筛选</text>
+        </view> -->
+        <view class="top-filter-box">
+            <van-tabs 
+                id="tabs"
+                :ellipsis="false" 
+                :active="activeId" 
+                color="#E3B377"
+                line-width="18px"
+                @change="onTabsChange"
+            >
+                <van-tab 
+                    :title="item.SectionName" 
+                    :name="item.SectionId" 
+                    v-for="item in tabsList" 
+                    :key="item.SectionId"
+                ></van-tab>
+            </van-tabs>
         </view>
           
+          
         <view class="empty-box" v-if="list.length==0&&finished">
             <image
                 :src="globalImgUrls.activityNoAuth"
@@ -12,30 +31,36 @@
             />
             <view>暂无数据</view>
         </view>
-        <view class="list-wrap" :style="{paddingBottom:pagePaddingBot}" v-else>
-            <view class="item" v-for="item in list" :key="item.BroadcastId">
+        <view class="list-wrap" :class="showAudioPop?showAudioBigPop?'list-bot3':'list-bot2':'list-bot1'" v-else>
+            <view class="item" v-for="item in list" :key="item.BroadcastId" @click="handleGoDetail(item)">
                 <view class="title">{{item.BroadcastName}}</view>
-                <view class="time">发布时间:{{item.CreateTime|formatTime}}</view>
-                <view class="flex audio-box" @click="handlePlay(item)">
+                <view class="time">发布时间:{{item.PublishTime|formatTime}}</view>
+                <view class="flex audio-box" @click.stop="handlePlay(item)">
                     <image 
-                        :src="item.VoiceUrl==curVoiceUrl&&item.BroadcastId==curVoiceId&&temAudio.play?require('@/static/voice/playing.png'):require('@/static/voice/pause.png')" 
+                        :src="item.BroadcastId==curVoiceId&&!curAudioPaused?require('@/static/voice/playing.png'):require('@/static/voice/pause.png')" 
                         mode="widthFix" 
                     />
                     <text>{{item.VoicePlaySeconds|formatVoiceTime}}</text>
                 </view>
-                <image class="del-btn" src="@/static/voice/del.png" @click="handleDelItem(item)" mode="widthFix" v-if="item.IsAuthor" />
+                <button class="publish-btn" @click.stop="handleSendMsgItem(item)" v-if="item.CouldSendMsg">
+                    <image class="publish-img" src="@/static/voice/publish.png" mode="widthFix"/>
+                </button>
+                <button class="del-btn" @click.stop="handleDelItem(item)" v-if="item.IsAuthor">
+                    <image class="del-img" src="@/static/voice/del.png" mode="widthFix"/>
+                </button>
                 <button 
                     class="share-btn" 
                     open-type="share" 
                     :data-item="item"
+                    @click.stop=""
                 >
                     <image class="share-img" src="@/static/share-icon.png" mode="aspectFill"/>
                 </button>
             </view>
         </view>
 
-        <navigator url="/pages-voice/addVoice">
-            <view :class="['add-btn',temAudio.item?temAudio.showBig?'add-btn-bot3':'add-btn-bot2':'add-btn-bot1']" v-if="IsVoiceAdmin">新建语音</view>
+        <navigator url="/pages-voice/addVoice" v-if="IsVoiceAdmin">
+            <view :class="['add-btn',showAudioPop?showAudioBigPop?'add-btn-bot3':'add-btn-bot2':'add-btn-bot1']">新建语音</view>
         </navigator>
 
         <!-- 筛选弹窗 -->
@@ -64,13 +89,20 @@
         </van-popup>
 
         <!-- 音频悬浮 -->
-        <voiceBox 
-            @stateChange="handleVoiceStateChange" 
-            @popChange="handleVoicePopChange"
-            @closeVoice="handleCloseVoice" 
-            :voiceData="temAudio.item" 
-            v-if="temAudio.item"
-        />
+        <view v-if="showPage">
+            <audioBox v-if="showAudioPop"/>
+        </view>
+        
+        <!-- 跳转去提问悬浮按钮 -->
+        <dragButton :existTabBar="true">
+            <navigator url="/pages-question/hasQuestion">
+                <view class="to-question-fixed-box">
+                    <image src="@/static/toquestion-icon.png" mode="widthFix" />
+                    <text>我要提问</text>
+                </view>
+            </navigator>
+        </dragButton>
+        
 
         <van-dialog id="van-dialog" />
     </view>
@@ -78,15 +110,17 @@
 </template>
 
 <script>
-import {apiVoiceList,apiVoiceSectionList,apiVoicePlayRecord,apiVoiceDel} from '@/api/voice'
+import {apiVoiceList,apiVoiceSectionList,apiVoicePlayRecord,apiVoiceDel,apiVoiceSendMsg} from '@/api/voice'
 import {apiGetSceneToParams} from '@/api/common'
 import noAuth from './components/noAuth.vue'
-import voiceBox from '@/components/voiceBox/voiceBox.vue'
+import audioBox from '@/components/audioBox/audioBox.vue'
+import dragButton from '@/components/dragButton/dragButton.vue'
 const dayjs=require('@/utils/dayjs.min')
 export default {
     components:{
         noAuth,
-        voiceBox
+        audioBox,
+        dragButton
     },
     filters:{
         formatTime(e){
@@ -99,31 +133,18 @@ export default {
         }
     },
     computed:{
-        curVoiceUrl(){
-            return this.temAudio.item&&this.temAudio.item.url||""
+        showAudioPop(){//是否显示音频弹窗
+            return this.$store.state.audio.show
         },
-        curVoiceId(){
-            return this.temAudio.item&&this.temAudio.item.id||""
+        showAudioBigPop(){
+            return this.$store.state.audio.showBig
+        },
+        curVoiceId(){//当前正在播放的音频id
+            return this.$store.state.audio.voiceId
+        },
+        curAudioPaused(){//当前音频是否暂停状态
+            return this.$store.state.audio.paused
         },
-        pagePaddingBot(){
-            let num=34
-            if(this.IsVoiceAdmin){
-                num=num+160
-            }else{
-                num=34
-            }
-            if(this.temAudio.item){
-                if(this.temAudio.showBig){
-                    num=num+260
-                }else{
-                    num=num+180
-                }
-            }else{
-                num=num+0
-            }
-
-            return num+'rpx'
-        }
     },
     data() {
         return {
@@ -142,36 +163,41 @@ export default {
             options:[],
             mainActiveIndex:0,
             activeId:0,//选择的板块id
+            tabsList:[],//顶部滑动筛选数据
 
-            temAudio:{
-                item:null,
-                play:false,
-                showBig:false
-            }
+            showPage:false,
         }
     },
     onLoad(options){
         this.init(options)
-        this.getOptionsList()
         this.addListenVoiceSuccess()
     },
     onShow(){
         //无权限时刷新列表
         if(!this.isAuth){
-            this.getVoiceList()
+            this.getOptionsList()
+        }else{
+            this.$nextTick(()=>{
+                this.selectComponent('#tabs').resize();// 解决初始渲染 vant tab 底部条
+            })
         }
+        
+        this.showPage=true
+    },
+    onHide(){
+        this.showPage=false
     },
     onUnload(){
 		uni.$off('addVoiceSuccess')
 	},
     onShareAppMessage({from,target}) {
         console.log(from,target);
-        let path='/pages/voice/voice?voiceId=0'
+        let path='/pages/voice/voice'
         let title='语音播报'
         let imageUrl=''
         if(from=='button'){
             title=`${target.dataset.item.SectionName}:${target.dataset.item.BroadcastName}`
-            path=`/pages/voice/voice?voiceId=${target.dataset.item.BroadcastId}`
+            path=`/pages-voice/voiceDetail?voiceId=${target.dataset.item.BroadcastId}`
             imageUrl=target.dataset.item.ImgUrl
         }
         return {
@@ -185,7 +211,6 @@ export default {
         this.page=1
         this.list=[]
         this.finished=false
-        this.getVoiceList()
         this.getOptionsList()
         setTimeout(() => {
             uni.stopPullDownRefresh()
@@ -198,6 +223,12 @@ export default {
     },
 
     methods: {
+        handleGoDetail(item){
+            uni.navigateTo({
+                url: '/pages-voice/voiceDetail?voiceId='+item.BroadcastId,
+            });
+        },
+
         // 监听添加音频成功刷新列表
         addListenVoiceSuccess(){
             uni.$on('addVoiceSuccess',()=>{
@@ -220,7 +251,7 @@ export default {
             }else{
                 this.voiceId=options.voiceId||0
             }
-            this.getVoiceList()
+            this.getOptionsList()
         },
 
         // 获取音频列表
@@ -254,6 +285,7 @@ export default {
                         this.getOptionsList()
                     }, 1500);
                 }
+                this.isAuth=true
             }else if(res.code===403){
                 //无权限用户
                 this.isAuth=false
@@ -266,6 +298,7 @@ export default {
             const res=await apiVoiceSectionList()
             if(res.code!==200) return
             const arr=res.data||[]
+            let temarr=[]
             this.options=arr.map(item=>{
                 let obj={
                     text:'',
@@ -273,6 +306,7 @@ export default {
                 }
                 obj.text=item.VarietyName
                 obj.children=item.Children.map(_item=>{
+                    temarr.push(_item)
                     return {
                         text:_item.SectionName,
                         id:_item.SectionId
@@ -280,6 +314,22 @@ export default {
                 })
                 return obj
             })
+            this.tabsList=temarr||[]
+            this.activeId=temarr[0].SectionId
+            this.$nextTick(()=>{
+                this.selectComponent('#tabs')?.resize();// 解决初始渲染 vant tab 底部条
+            })
+            this.getVoiceList()
+        },
+
+        // 顶部tab切换
+        onTabsChange(e){
+            this.activeId=e.detail.name
+            this.voiceId=0
+            this.page=1
+            this.list=[]
+            this.finished=false
+            this.getVoiceList()
         },
 
         onClickNav({detail}){
@@ -301,6 +351,25 @@ export default {
             this.getVoiceList()
             this.showFilter=false
         },
+
+        //推送消息
+        handleSendMsgItem(item){
+            this.$dialog.confirm({
+                title:'',
+                message: '该操作将推送模板消息和客群,确认推送吗?',
+                confirmButtonText:'确认'
+            }).then(()=>{
+                apiVoiceSendMsg({broadcast_id:item.BroadcastId}).then(res=>{
+                    if(res.code===200){
+                        uni.showToast({
+                            title:"推送成功",
+                            icon:'success'
+                        })
+                        item.CouldSendMsg=false
+                    }
+                })
+            }).catch(()=>{})
+        },
         
         //删除音频
         handleDelItem(item){
@@ -309,7 +378,7 @@ export default {
                 message: '确定要删除该语音播报吗?',
                 confirmButtonText:'确定'
             }).then(()=>{
-                if(this.temAudio.item&&this.temAudio.item.id==item.BroadcastId){
+                if(this.curVoiceId==item.BroadcastId&&!this.curAudioPaused){
                     //删除的音频正好在播放则暂停
                     this.globalBgMusic.stop()
                 }
@@ -317,7 +386,7 @@ export default {
                 apiVoiceDel({broadcast_id:Number(item.BroadcastId)}).then(res=>{
                     if(res.code===200){
                         uni.showToast({
-                            message:'操作成功',
+                            title:'操作成功',
                             icon:'none'
                         })
                         this.page=1
@@ -331,36 +400,20 @@ export default {
 
         //点击音频 播放或者暂停
         handlePlay(item){
-            if(!this.temAudio.item||this.temAudio.item.id!=item.BroadcastId){
-                this.temAudio.item={
-                    title:item.BroadcastName,
-                    duration:item.VoicePlaySeconds,
-                    url:item.VoiceUrl,
-                    id:item.BroadcastId
-                }
-                this.handleVoicePlayRecord(item)
-            }else{
+            if(this.$store.state.audio.voiceId==item.BroadcastId){
                 if(this.globalBgMusic.paused){
                     this.globalBgMusic.play()
                 }else{
                     this.globalBgMusic.pause()
                 }
+            }else{
+                const list=[{url:item.VoiceUrl,time:item.VoicePlaySeconds,title:item.BroadcastName,}]
+                this.$store.commit('audio/addAudio',{
+                    list:list,
+                    voiceId:item.BroadcastId
+                })
+                this.handleVoicePlayRecord(item)
             }
-            
-        },
-        //关闭音频
-        handleCloseVoice(){
-            this.temAudio.item=null
-        },
-        
-        //音频播放状态
-        handleVoiceStateChange(e){
-            this.temAudio.play=e
-        },
-
-        //音频弹窗大小变化
-        handleVoicePopChange(e){
-            this.temAudio.showBig=e
         },
 
         //上报音频播放记录
@@ -377,6 +430,17 @@ export default {
 </script>
 
 <style lang="scss">
+.top-filter-box{
+    .van-tab{
+        font-size: 28rpx;
+        color: #777;
+    }
+    .van-tab--active{
+        font-weight: bold;
+        font-size: 32rpx;
+        color: #333333;
+    }
+}
 .voice-play-page{
     
     .fliter-wrap-list{
@@ -408,22 +472,19 @@ export default {
 <style lang="scss" scoped>
 .voice-play-page{
     .top-filter-box{
-        display: flex;
-		flex: auto;
-		align-items: center;
-		height: 100rpx;
+        
 		background-color: white;
+        box-shadow: 0px 4rpx 4rpx 0px rgba(198,198,198,0.2500);
 		position: sticky;
 		top: 0;
 		left: 0;
-		padding-left: 34rpx;
 		z-index: 99;
-        image{
-            width: 34rpx;
-            height: 34rpx;
-        }
-        color: #E3B377;
-        font-size: 28rpx;
+        // image{
+        //     width: 34rpx;
+        //     height: 34rpx;
+        // }
+        // color: #E3B377;
+        // font-size: 28rpx;
     }
 }
 .empty-box{
@@ -442,17 +503,45 @@ export default {
         border-bottom: 1px solid #CDCDCD;
         padding: 30rpx 0;
         position: relative;
+        .publish-btn{
+            position: absolute;
+            right: 192rpx;
+            bottom: 30rpx;
+            width: 36rpx;
+            height: 36rpx;
+            background-color: transparent;
+            line-height: 1;
+            padding: 0;
+            &::after{
+                border: none;
+            }
+        }
+        .publish-img{
+            width: 34rpx;
+            height: 34rpx;
+        }
         .del-btn{
             position: absolute;
-            right: 130rpx;
+            right: 96rpx;
             bottom: 30rpx;
-            width: 32rpx;
-            height: 32rpx;
+            width: 36rpx;
+            height: 36rpx;
+            background-color: transparent;
+            line-height: 1;
+            padding: 0;
+            &::after{
+                border: none;
+            }
+        }
+        .del-img{
+            width: 34rpx;
+            height: 34rpx;
         }
+
         .share-btn{
             position: absolute;
             bottom: 30rpx;
-            right: 34rpx;
+            right: 0rpx;
             background-color: transparent;
             width: 36rpx;
             height: 36rpx;
@@ -490,7 +579,15 @@ export default {
             }
         }
     }
-
+}
+.list-bot1{
+	padding-bottom: 200rpx;
+}
+.list-bot2{
+	padding-bottom: 340rpx;
+}
+.list-bot3{
+	padding-bottom: 440rpx;
 }
 .add-btn{
     position: fixed;

BIN
static/sandTable/sandBox-share-icon.png


BIN
static/toquestion-icon.png


BIN
static/voice/mine-voice-icon.png


BIN
static/voice/publish.png


+ 3 - 1
store/index.js

@@ -3,6 +3,7 @@ import Vuex from 'vuex'
 import user from './modules/user.js'
 import activity from './modules/activity'
 import report from './modules/report'
+import audio from './modules/audio'
 Vue.use(Vuex);//vue的插件机制
 
 //Vuex.Store 构造器选项
@@ -13,7 +14,8 @@ const store = new Vuex.Store({
 	modules:{
 		user,
 		activity,
-		report
+		report,
+		audio
 	}
 })
 export default store

+ 58 - 0
store/modules/audio.js

@@ -0,0 +1,58 @@
+// 全局音频背景播放状态管理模块
+const audioModules={
+    namespaced: true,
+    state:{
+        show:false,//是否显示音频弹窗
+        showBig:false,//显示大弹窗
+        list:[],//[{url:音频地址,time:音频时长,title:音频标题,}]
+        index:0,//当前是播放第几个
+        reportId:0,//当前是哪个报告的音频
+        voiceId:0,//当前是哪个语音播报的音频
+        questionId:0,//当前是哪个问答的音频
+        paused:true,//当前是否音频正在播放 true暂停状态
+        curTime:0,//当前正在播放的音频播放的时间
+    },
+    mutations: {
+        addAudio(state,e){
+            state.show=true
+            state.list=e.list
+            state.index=0
+            state.reportId=e.reportId||0
+            state.voiceId=e.voiceId||0
+            state.questionId=e.questionId||0
+        },
+        updateAudioIndex(state,e){
+            state.index=e
+        },
+        // 音频状态
+        updateAudioPause(state,e){
+            state.paused=e
+        },
+        // 更新音频播放进度
+        updateAudioTime(state,e){
+            state.curTime=e
+        },
+        removeAudio(state,e){
+            state.show=false
+            state.list=[]
+            state.index=0
+            state.reportId=0
+            state.voiceId=0
+            state.questionId=0
+            state.paused=true
+        },
+        //显示弹窗
+        showPopAudio(state){
+            state.show=true
+        },
+        showBig(state,e){
+            state.showBig=e
+        },
+        // 关闭弹窗
+        closePopAudio(state){
+            state.show=false
+        }
+    }
+}
+
+export default audioModules;

+ 23 - 0
style/common.scss

@@ -197,4 +197,27 @@ view{
         line-height: 80rpx;
         border-radius: 40rpx;
     }
+}
+
+// 跳转去提问的悬浮按钮
+.to-question-fixed-box{
+    width: 162rpx;
+    height: 145rpx;
+    background: #FFFFFF;
+    box-shadow: 0px 4px 17px 0px rgba(0,0,0,0.08);
+    border: 1px solid #F2F2F2;
+    display: flex;
+    align-items: center;
+    flex-direction: column;
+    justify-content: center;
+    border-radius: 16rpx;
+    image{
+        width: 84rpx;
+        height: 84rpx;
+    }
+    text{
+        font-size: 28rpx;
+        font-weight: 400;
+        color: #E3B377;
+    }
 }

+ 1 - 1
utils/config.js

@@ -11,7 +11,7 @@ if(env.envVersion==='develop'){//开发
 	 // h5BaseUrl='http://192.168.77.17:3000/xcx_h5'
     pcBaseUrl='https://ybpctest.hzinsights.com'
 }else if(env.envVersion==='trial'){//体验版
-    baseApiUrl='http://8.136.199.33:8612/api'
+    baseApiUrl='https://ybpctest.hzinsights.com/api'
     h5BaseUrl='http://xcxh5test.hzinsights.com/xcx_h5'
     pcBaseUrl='https://ybpctest.hzinsights.com'
 }else if(env.envVersion==='release'){//正式版

+ 9 - 8
utils/request.js

@@ -35,6 +35,7 @@ const wechatLogin=()=>{
 				apiWechatLogin({code:loginRes.code}).then(res=>{
 					if(res.code===200){
 						store.commit('setToken', res.data.authorization)
+						store.dispatch('getUserInfo')
 						resolve(res.data)
 					}
 				})
@@ -61,13 +62,13 @@ const refreshToken=async (url,params,method,resolve)=>{
 		isRefreshing=true
 		const wechatLoginRes=await wechatLogin()
 		console.log(wechatLoginRes);
-		if(!wechatLoginRes.is_bind){
-			uni.reLaunch({
-				url:'/pages/login'
-			})
-			isRefreshing=false
-			return
-		}
+		// if(!wechatLoginRes.is_bind){
+		// 	uni.reLaunch({
+		// 		url:'/pages/login'
+		// 	})
+		// 	isRefreshing=false
+		// 	return
+		// }
 		// 重新请求队列
 		requestList.map(MT=>{MT()})
 		requestList=[]
@@ -95,7 +96,7 @@ const http=(url,params,method)=>{
 			method:method,
 			header:{
 				Authorization:store.state.user.token,
-				version:'yb9.0'
+				version:'yb11.0'
 			},
 			success(e) {
 				// 接口404

+ 43 - 1
utils/upload.js

@@ -49,7 +49,8 @@ export const uploadAudioToServer = async(tempFilePath)=>{
  * 上传图片
  * count 同时上传张数 默认:1
  */
-export const uploadImg = async (count = 1) => {
+export const uploadImg = async ({count = 1}) => {
+  console.log(count);
   const { tempFilePaths } = await uniAsync.chooseImage({ count });
   uni.showLoading({
     title: "上传中...",
@@ -68,6 +69,47 @@ export const uploadImg = async (count = 1) => {
       })
       .catch((res) => {
         uni.hideLoading();
+        reject(res)
       });
   });
 };
+
+
+/**
+ * 公共上传音频方法
+ */
+export const commonUploadAudio=(tempFilePath)=>{
+  const { envVersion } = uni.getAccountInfoSync().miniProgram
+  wx.showLoading({
+      title: '上传音频中...',
+      mask: true,
+      success: (result) => {},
+      fail: () => {},
+      complete: () => {}
+  });
+  return new Promise((resolve,reject)=>{
+    uni.uploadFile({
+      url: baseApiUrl + "/public/upload_audio",
+      filePath: tempFilePath,
+      name: 'file',
+      header: {
+        Authorization: store.state.user.token,
+      },
+      success: (result) =>{
+        const res =  envVersion === 'release' ? JSON.parse(CryptoJS.Des3Decrypt(result.data)) :  JSON.parse(result.data);
+        resolve(res)
+      },
+      fail: () => {
+        console.log('上传音频失败');
+        reject('上传音频失败')
+        uni.showToast({
+            title:'上传音频失败,请稍后重试!',
+            icon:'none'
+        })
+      },
+      complete: () => {
+        wx.hideLoading();
+      }
+    })
+  })
+}