Bläddra i källkod

Merge branch 'master' into 康年堂

ct 3 dagar sedan
förälder
incheckning
ef6628c042

+ 1 - 1
.env.prod-heyantang

@@ -32,7 +32,7 @@ VUE_APP_VIDEO_URL = https://cdhytvolcengine.ylrztop.com
 VUE_APP_HSY_SPACE = cdhyt-2114522511
 
 #直播解码路径
-LIVE_PATH = /live
+VUE_APP_LIVE_PATH = /live
 # 开发环境配置
 ENV = 'production'
 

+ 2 - 2
src/api/live/liveData.js

@@ -28,9 +28,9 @@ export function getLiveDataDetailBySql(liveId) {
   })
 }
 
-export function getLiveUserDetailListBySql(liveId) {
+export function getLiveUserDetailListBySql(liveId, pageNum, pageSize) {
   return request({
-    url: '/liveData/liveData/getLiveUserDetailListBySql?liveId=' + liveId,
+    url: '/liveData/liveData/getLiveUserDetailListBySql?liveId=' + liveId + '&pageNum=' + (pageNum || 1) + '&pageSize=' + (pageSize || 100),
     method: 'get'
   })
 }

+ 11 - 2
src/utils/cos.js

@@ -39,6 +39,7 @@ export const uploadObject = async (file, onProgress, type, callBackUp) => {
     })
 
     console.log("初始化成功")
+    console.log(process.env)
     const fileName = file.name || ""
     const upload_file_name = new Date().getTime() + "." + fileName.split(".")[fileName.split(".").length - 1]
     const date = new Date()
@@ -48,8 +49,16 @@ export const uploadObject = async (file, onProgress, type, callBackUp) => {
     const uploadDay = `${year}${month}${strDate}`
     const videoKey = `/userVideo/${uploadDay}/${upload_file_name}`
     const courseKey = `/course/${uploadDay}/${upload_file_name}`
-    const basePath = process.LIVE_PATH || '/course';
-    const liveKey = `${basePath}/${uploadDay}/${upload_file_name}`;
+    let liveKey;
+    console.log("LIVE_PATH value:", process.env.VUE_APP_LIVE_PATH);  // 添加这行查看实际值
+    console.log("Type of LIVE_PATH:", typeof process.env.VUE_APP_LIVE_PATH);  // 查看类型
+// 检查是否是空字符串或只有空格
+    if (process.env.VUE_APP_LIVE_PATH && process.env.VUE_APP_LIVE_PATH.trim() !== '') {
+      liveKey = `${process.env.VUE_APP_LIVE_PATH.trim()}/${uploadDay}/${upload_file_name}`;
+    } else {
+      liveKey = `/course/${uploadDay}/${upload_file_name}`;
+    }
+    console.log("liveKey={}",liveKey);
     const key = type === 3 ? liveKey : (type === 1 ? courseKey : videoKey);
 
     console.log("开始上传")

+ 3 - 3
src/views/course/coursePlaySourceConfig/index.vue

@@ -589,13 +589,13 @@ export default {
       this.bindCurrentRow = row;  // 保存当前行数据
       this.bindForm.bindShow = true;
     },
-    changeSysPayModes(value){
-      console.log(value)
+    changeSysPayModes(value,row){
       const query = {
         pageNum: 1,
         pageSize: 100,
         merchantType: value,
-        isDeleted: 0
+        isDeleted: 0,
+        appId:this.bindCurrentRow.appid
       }
       listMerchantAppConfig(query).then( response => {
           this.merchantAppConfigList = response.rows;

+ 68 - 152
src/views/live/liveConsole/LivePlayer.vue

@@ -1,22 +1,9 @@
 <template>
   <div class="live-player">
-    <!-- 修改:所有录播和回放都使用同一个video元素,MP4和HLS都支持 -->
-    <video
-      v-if="videoParam.liveType == 2 || videoParam.liveType == 3"
-      ref="videoPlayer"
-      :loop="videoParam.liveType == 2"
-      autoplay
-      class="player"
-    >
-      <!-- 去掉type,让浏览器自动检测 -->
+    <video v-if="videoParam.liveType == 2" ref="videoPlayer" loop autoplay class="player">
+      <source :src="videoParam.videoUrl" type="application/x-mpegURL">
     </video>
-
-    <video
-      v-if="videoParam.liveType == 1"
-      ref="livePlayer"
-      autoplay
-      class="player"
-    >
+    <video v-if="videoParam.liveType == 1" ref="livePlayer" loop autoplay class="player">
     </video>
   </div>
 </template>
@@ -40,33 +27,23 @@ export default {
   },
   data() {
     return {
-      videoDuration: 0,
-      startTime: 0,
-      hls: null,
-      liveHls: null // 分开管理直播和录播的HLS实例
+      videoDuration: 0, // 视频总时长(秒)
+      startTime: 0, // 开播时间戳(毫秒)
+      hls: null, // HLS 实例
     };
   },
   mounted() {
-    this.initPlayer();
+
+    // 这里可以添加播放器初始化逻辑
   },
   methods: {
     videoPlay(url) {
-      const videoElement = this.$refs.videoPlayer;
-      if (!videoElement) {
-        console.error('找不到 video 元素');
-        return;
-      }
-
-      // 判断是否是MP4格式
-      const isMp4 = url.toLowerCase().endsWith('.mp4') ||
-        url.toLowerCase().includes('.mp4?');
-
-      // 判断是否是HLS格式(m3u8)
-      const isHls = url.toLowerCase().endsWith('.m3u8') ||
-        url.toLowerCase().includes('.m3u8?');
-
-      if (isHls && Hls.isSupported()) {
-        // HLS格式使用hls.js播放
+      if (Hls.isSupported()) {
+        const videoElement = this.$refs.videoPlayer
+        if (!videoElement) {
+          console.error('找不到 video 元素')
+          return
+        }
         this.hls = new Hls();
         this.hls.attachMedia(videoElement);
         this.hls.on(Hls.Events.MEDIA_ATTACHED, () => {
@@ -78,165 +55,104 @@ export default {
         this.hls.on(Hls.Events.ERROR, (event, data) => {
           console.error('HLS 错误:', data);
         });
-
-        // 录播的时间位置计算
+        // 1. 初始化开播时间
+        this.startTime = new Date(this.videoParam.startTime).getTime();
         if (this.videoParam.liveType === 2) {
+          // 2. 监听视频元数据加载完成(获取视频时长)
           videoElement.addEventListener('loadedmetadata', () => {
-            this.videoDuration = videoElement.duration;
-            this.updateVideoPosition(videoElement);
-          });
-        }
+            this.videoDuration = videoElement.duration; // 获取视频时长(秒)
 
-      } else if (isMp4 || !isHls) {
-        // MP4格式或非HLS格式直接使用video元素播放
-        videoElement.src = url;
-
-        // 录播的时间位置计算
-        if (this.videoParam.liveType === 2) {
-          videoElement.addEventListener('loadedmetadata', () => {
-            this.videoDuration = videoElement.duration;
+            // 初始化视频播放位置
             this.updateVideoPosition(videoElement);
+
           });
         }
-
-        // 触发播放
-        videoElement.load();
-        videoElement.play().catch(e => {
-          console.warn('自动播放失败:', e);
-        });
-
       } else {
-        // 不支持HLS的浏览器,尝试直接播放
-        console.warn('浏览器不支持HLS,尝试直接播放');
-        videoElement.src = url;
-        videoElement.load();
-        videoElement.play().catch(e => {
-          console.warn('播放失败:', e);
-        });
+        console.error('浏览器不支持 HLS')
       }
     },
-
-    updateVideoPosition(video) {
-      const currentTime = new Date().getTime();
-      this.startTime = new Date(this.videoParam.startTime).getTime();
-      const elapsedTime = currentTime - this.startTime;
-
+    updateVideoPosition(video){
+      const currentTime = new Date().getTime(); // 当前时间戳(毫秒)
+      const elapsedTime = currentTime - this.startTime; // 已流逝时间(毫秒)
       if (elapsedTime < 0) {
+        // 未开播:视频停在初始位置
         video.currentTime = 0;
         return;
       }
 
-      const elapsedSeconds = elapsedTime / 1000;
-      if (this.videoDuration > 0) {
-        video.currentTime = elapsedSeconds % this.videoDuration;
-      }
+      // 已开播:计算视频循环后的位置(流逝时间 % 视频时长)
+      const elapsedSeconds = elapsedTime / 1000; // 转换为秒
+      // 视频内的播放位置(秒)
+      // 设置视频播放位置
+      video.currentTime = elapsedSeconds % this.videoDuration;
     },
-
     livePlay(url) {
       if (Hls.isSupported()) {
-        const videoElement = this.$refs.livePlayer;
+        const videoElement = this.$refs.livePlayer
         if (!videoElement) {
-          console.error('找不到 video 元素');
-          return;
+          console.error('找不到 video 元素')
+          return
         }
-
-        this.liveHls = new Hls();
-        this.liveHls.attachMedia(videoElement);
-        this.liveHls.on(Hls.Events.MEDIA_ATTACHED, () => {
-          this.liveHls.loadSource(url);
-          this.liveHls.on(Hls.Events.STREAM_LOADED, (event, data) => {
+        this.hls = new Hls();
+        this.hls.attachMedia(videoElement);
+        this.hls.on(Hls.Events.MEDIA_ATTACHED, () => {
+          this.hls.loadSource(url);
+          this.hls.on(Hls.Events.STREAM_LOADED, (event, data) => {
             videoElement.play();
           });
         });
-        this.liveHls.on(Hls.Events.ERROR, (event, data) => {
+        this.hls.on(Hls.Events.ERROR, (event, data) => {
           console.error('HLS 错误:', data);
         });
-
       } else {
-        // 浏览器原生支持HLS(如Safari)
-        const videoElement = this.$refs.livePlayer;
-        if (videoElement.canPlayType('application/vnd.apple.mpegurl')) {
-          videoElement.src = url;
-          videoElement.play().catch(e => {
-            console.warn('自动播放失败:', e);
-          });
-        } else {
-          console.error('浏览器不支持 HLS');
-        }
+        console.error('浏览器不支持 HLS')
       }
     },
-
     initPlayer() {
-      // 清理之前的实例
-      if (this.hls) {
-        this.hls.destroy();
-        this.hls = null;
-      }
-      if (this.liveHls) {
-        this.liveHls.destroy();
-        this.liveHls = null;
-      }
 
       if (this.videoParam.liveType === 1) {
         // 直播
         const isUrl = this.videoParam.livingUrl === null || this.videoParam.livingUrl.trim() === '';
         if (isUrl) {
-          console.error('直播地址为空,无法初始化播放器');
-          return;
+          console.error('直播地址为空,无法初始化播放器')
+          return
         }
         this.$nextTick(() => {
           this.livePlay(this.videoParam.livingUrl);
-        });
+        })
         return;
       }
-
-      const viUrl = this.videoParam.videoUrl === null || this.videoParam.videoUrl.trim() === '';
+      const viUrl = this.videoParam.videoUrl === null || this.videoParam.videoUrl.trim() === ''
       if (viUrl) {
-        console.error('播放地址为空,无法初始化播放器');
-        return;
+        console.error('播放地址为空,无法初始化播放器')
+        return
       }
-
-      if (this.videoParam.liveType === 2 || this.videoParam.liveType === 3) {
-        // 录播或直播回放
-        this.$nextTick(() => {
-          this.videoPlay(this.videoParam.videoUrl);
-        });
-      } else {
-        console.error('直播类型错误,无法初始化播放器');
+      if(this.videoParam.liveType === 2){
+        // 录播
+        this.videoPlay(this.videoParam.videoUrl);
+      } else if(this.videoParam.liveType === 3){
+        // 直播回放
+        this.videoPlay(this.videoParam.videoUrl);
+      }else {
+        console.error('直播类型错误,无法初始化播放器')
       }
+      // 创建播放器实例
+      const videoPlayer = this.$refs.videoPlayer;
+      // 添加播放器事件监听器
+      videoPlayer.addEventListener('play', () => {
+        console.log('播放器已开始播放');
+      });
+      videoPlayer.addEventListener('pause', () => {
+        console.log('播放器已暂停');
+      });
+      videoPlayer.addEventListener('ended', () => {
+        console.log('播放器已结束');
+      });
     }
   },
-
-  watch: {
-    // 监听videoParam变化,重新初始化播放器
-    videoParam: {
-      handler() {
-        this.$nextTick(() => {
-          this.initPlayer();
-        });
-      },
-      deep: true
-    }
-  },
-
   beforeDestroy() {
-    // 销毁HLS实例
-    this.hls?.destroy();
-    this.liveHls?.destroy();
-
-    // 清理video元素
-    const videoElements = [
-      this.$refs.videoPlayer,
-      this.$refs.livePlayer
-    ];
-
-    videoElements.forEach(video => {
-      if (video) {
-        video.pause();
-        video.src = '';
-        video.load();
-      }
-    });
+    this.hls?.destroy()
+    // 销毁播放器实例
   }
 };
 </script>

+ 31 - 10
src/views/live/liveData/index.vue

@@ -363,25 +363,25 @@
             </el-col>
             <el-col :span="12">
               <div class="detail-item">
-                <span class="detail-label">>20分钟人数(直播):</span>
+                <span class="detail-label">>=20分钟人数(直播):</span>
                 <span class="detail-value">{{ detailData.liveOver20Minutes || 0 }}</span>
               </div>
             </el-col>
             <el-col :span="12">
               <div class="detail-item">
-                <span class="detail-label">>30分钟人数(直播):</span>
+                <span class="detail-label">>=30分钟人数(直播):</span>
                 <span class="detail-value">{{ detailData.liveOver30Minutes || 0 }}</span>
               </div>
             </el-col>
             <el-col :span="12">
               <div class="detail-item">
-                <span class="detail-label">到课完课率直播(>20分钟):</span>
+                <span class="detail-label">到课完课率直播(>=20分钟):</span>
                 <span class="detail-value">{{ formatPercent(detailData.liveCompletionRate20) }}</span>
               </div>
             </el-col>
             <el-col :span="12">
               <div class="detail-item">
-                <span class="detail-label">到课完课率直播(>30分钟):</span>
+                <span class="detail-label">到课完课率直播(>=30分钟):</span>
                 <span class="detail-value">{{ formatPercent(detailData.liveCompletionRate30) }}</span>
               </div>
             </el-col>
@@ -399,25 +399,25 @@
             </el-col>
             <el-col :span="12">
               <div class="detail-item">
-                <span class="detail-label">>20分钟人数(回放):</span>
+                <span class="detail-label">>=20分钟人数(回放):</span>
                 <span class="detail-value">{{ detailData.playbackOver20Minutes || 0 }}</span>
               </div>
             </el-col>
             <el-col :span="12">
               <div class="detail-item">
-                <span class="detail-label">>30分钟人数(回放):</span>
+                <span class="detail-label">>=30分钟人数(回放):</span>
                 <span class="detail-value">{{ detailData.playbackOver30Minutes || 0 }}</span>
               </div>
             </el-col>
             <el-col :span="12">
               <div class="detail-item">
-                <span class="detail-label">到课完课率回放(>20分钟):</span>
+                <span class="detail-label">到课完课率回放(>=20分钟):</span>
                 <span class="detail-value">{{ formatPercent(detailData.playbackCompletionRate20) }}</span>
               </div>
             </el-col>
             <el-col :span="12">
               <div class="detail-item">
-                <span class="detail-label">到课完课率回放(>30分钟):</span>
+                <span class="detail-label">到课完课率回放(>=30分钟):</span>
                 <span class="detail-value">{{ formatPercent(detailData.playbackCompletionRate30) }}</span>
               </div>
             </el-col>
@@ -555,6 +555,16 @@
           <el-table-column prop="companyName" label="分公司" min-width="150"></el-table-column>
           <el-table-column prop="salesName" label="销售" min-width="120"></el-table-column>
         </el-table>
+        <!-- 用户详情分页组件 -->
+        <pagination
+          v-show="userDetailTotal > 0"
+          :total="userDetailTotal"
+          :page.sync="userDetailQueryParams.pageNum"
+          :limit.sync="userDetailQueryParams.pageSize"
+          :page-sizes="[10, 100, 1000]"
+          @pagination="handleViewUserDetail"
+          style="margin-top: 20px;"
+        />
       </div>
     </el-drawer>
   </div>
@@ -633,7 +643,12 @@ export default {
       userDetailList: [],
       userDetailLoading: false,
       showUserDetail: false,
-      userDetailExportLoading: false
+      userDetailExportLoading: false,
+      userDetailTotal: 0,
+      userDetailQueryParams: {
+        pageNum: 1,
+        pageSize: 10
+      }
     };
   },
   created() {
@@ -882,9 +897,10 @@ export default {
       if (!this.currentLiveId) return;
       this.showUserDetail = true;
       this.userDetailLoading = true;
-      getLiveUserDetailListBySql(this.currentLiveId).then(response => {
+      getLiveUserDetailListBySql(this.currentLiveId, this.userDetailQueryParams.pageNum, this.userDetailQueryParams.pageSize).then(response => {
         if (response.code === 200) {
           this.userDetailList = response.data || [];
+          this.userDetailTotal = response.total || 0;
         }
         this.userDetailLoading = false;
       }).catch(() => {
@@ -898,6 +914,11 @@ export default {
       this.detailData = {};
       this.userDetailList = [];
       this.currentLiveId = null;
+      this.userDetailTotal = 0;
+      this.userDetailQueryParams = {
+        pageNum: 1,
+        pageSize: 100
+      };
     },
     /** 格式化百分比 */
     formatPercent(value) {

+ 50 - 39
src/views/live/order/index.vue

@@ -161,37 +161,37 @@
 <!--        </el-select>-->
 <!--      </el-form-item>-->
 
-      <el-form-item label="结算状态" prop="deliveryPayStatus">
-        <el-select v-model="queryParams.deliveryPayStatus" placeholder="请选择物流结算状态" clearable size="small">
-          <el-option
-            v-for="item in deliveryPayStatusOptions"
-            :key="item.dictValue"
-            :label="item.dictLabel"
-            :value="item.dictValue"
-          />
-        </el-select>
-      </el-form-item>
+<!--      <el-form-item label="结算状态" prop="deliveryPayStatus">-->
+<!--        <el-select v-model="queryParams.deliveryPayStatus" placeholder="请选择物流结算状态" clearable size="small">-->
+<!--          <el-option-->
+<!--            v-for="item in deliveryPayStatusOptions"-->
+<!--            :key="item.dictValue"-->
+<!--            :label="item.dictLabel"-->
+<!--            :value="item.dictValue"-->
+<!--          />-->
+<!--        </el-select>-->
+<!--      </el-form-item>-->
 
-      <el-form-item label="小程序" prop="appId">
-        <el-select v-model="queryParams.appId" placeholder="请选择所属小程序" clearable size="small">
-          <el-option
-            v-for="dict in appMallOptions"
-            :key="dict.appid"
-            :label="dict.name + '(' + dict.appid + ')'"
-            :value="dict.appid"
-          />
-        </el-select>
-      </el-form-item>
+<!--      <el-form-item label="小程序" prop="appId">-->
+<!--        <el-select v-model="queryParams.appId" placeholder="请选择所属小程序" clearable size="small">-->
+<!--          <el-option-->
+<!--            v-for="dict in appMallOptions"-->
+<!--            :key="dict.appid"-->
+<!--            :label="dict.name + '(' + dict.appid + ')'"-->
+<!--            :value="dict.appid"-->
+<!--          />-->
+<!--        </el-select>-->
+<!--      </el-form-item>-->
 
-      <el-form-item label="商品规格" prop="productSpec">
-        <el-input
-          v-model="queryParams.productSpec"
-          placeholder="请输入商品规格"
-          clearable
-          size="small"
-          @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
+<!--      <el-form-item label="商品规格" prop="productSpec">-->
+<!--        <el-input-->
+<!--          v-model="queryParams.productSpec"-->
+<!--          placeholder="请输入商品规格"-->
+<!--          clearable-->
+<!--          size="small"-->
+<!--          @keyup.enter.native="handleQuery"-->
+<!--        />-->
+<!--      </el-form-item>-->
 
 <!--      <el-form-item label="商品数量" prop="totalNum">-->
 <!--        <el-input-->
@@ -232,6 +232,15 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
+      <el-form-item label="汇付商户订单号" prop="hfshh">
+        <el-input
+          v-model="queryParams.hfshh"
+          placeholder="请输入汇付商户订单号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
 
 <!--      <el-form-item label="成本价格" prop="cost">-->
 <!--        <el-input-->
@@ -253,16 +262,16 @@
 <!--        />-->
 <!--      </el-form-item>-->
 
-      <el-form-item label="档期归属" prop="scheduleId">
-        <el-select filterable style="width: 215px" v-model="queryParams.scheduleId" placeholder="请选择档期" clearable size="small">
-          <el-option
-            v-for="item in scheduleOptions"
-            :key="item.id"
-            :label="item.name"
-            :value="item.id"
-          />
-        </el-select>
-      </el-form-item>
+<!--      <el-form-item label="档期归属" prop="scheduleId">-->
+<!--        <el-select filterable style="width: 215px" v-model="queryParams.scheduleId" placeholder="请选择档期" clearable size="small">-->
+<!--          <el-option-->
+<!--            v-for="item in scheduleOptions"-->
+<!--            :key="item.id"-->
+<!--            :label="item.name"-->
+<!--            :value="item.id"-->
+<!--          />-->
+<!--        </el-select>-->
+<!--      </el-form-item>-->
 
       <el-form-item label="代服账户" prop="erpAccount" v-if="SFDFopen">
         <el-select v-model="queryParams.erpAccount" style="width: 215px" placeholder="ERP账户" clearable size="small">
@@ -447,6 +456,7 @@
       <el-table-column label="手机号" align="center" prop="userPhone" width="120px" />
       <el-table-column label="商品规格" align="center" prop="productSpec" width="120px" />
       <el-table-column label="商品数量" align="center" prop="totalNum" width="100px" />
+      <el-table-column label="汇付商户订单号" align="center" prop="hfshh" width="100px" />
       <el-table-column label="订单金额" align="center" prop="totalPrice" width="100px">
         <template slot-scope="scope">
           <span v-if="scope.row.totalPrice != null">{{ scope.row.totalPrice.toFixed(2) }}</span>
@@ -717,6 +727,7 @@ export default {
         pageSize: 10,
         companyId: null,
         deptId: null,
+        hfshh: null,
         orderTypeFilter: null,
         salesName: null,
         orderCodes: [],

+ 9 - 9
src/views/qw/qwCompany/index.vue

@@ -200,14 +200,14 @@
         <el-form-item label="聊天工具栏实际运用地址" prop="chatToolbarOauth">
           <el-input v-model="form.chatToolbarOauth" placeholder="请输入聊天工具栏实际运用地址" />
         </el-form-item>
-        <el-form-item label="app分享小程序原始id" prop="yjfyyAppId">
-          <el-input v-model="form.yjfyyAppId" placeholder="app分享小程序原始id" />
+        <el-form-item label="app分享小程序原始id" prop="shareAppId">
+          <el-input v-model="form.shareAppId" placeholder="app分享小程序原始id" />
         </el-form-item>
-        <el-form-item label="app分享小程序应用id" prop="yjfyyAgentId">
-          <el-input v-model="form.yjfyyAgentId" placeholder="app分享小程序应用id" />
+        <el-form-item label="app分享小程序应用id" prop="shareAgentId">
+          <el-input v-model="form.shareAgentId" placeholder="app分享小程序应用id" />
         </el-form-item>
-        <el-form-item label="app分享小程序schma" prop="yjfyySchema">
-          <el-input v-model="form.yjfyySchema" placeholder="app分享小程序schema" />
+        <el-form-item label="app分享小程序schma" prop="shareSchema">
+          <el-input v-model="form.shareSchema" placeholder="app分享小程序schema" />
         </el-form-item>
         <el-form-item label="关联公司" prop="companyIds">
 
@@ -369,9 +369,9 @@ export default {
         createTime: null,
         updateTime: null,
         createBy: null,
-        yjfyyAppId: null,
-        yjfyyAgentId: null,
-        yjfyySchema: null,
+        shareAppId: null,
+        shareAgentId: null,
+        shareSchema: null,
       };
       this.resetForm("form");
     },