ソースを参照

点播订单和看课记录

yuhongqi 6 日 前
コミット
19350278f5

+ 237 - 15
src/views/components/course/userCourseCatalogDetailsZM.vue

@@ -333,6 +333,62 @@
             <el-table-column label="商品名称" align="center" prop="productName"/>
             <el-table-column label="商品价格" align="center" prop="price"/>
             <el-table-column label="库存" align="center" prop="stock"/>
+            <el-table-column label="上架时间" align="center" prop="onShelfTime" width="180">
+              <template slot-scope="scope">
+                <el-time-picker
+                  v-model="scope.row.onShelfTime"
+                  value-format="HH:mm:ss"
+                  format="HH:mm:ss"
+                  placeholder="选择时间"
+                  size="mini"
+                  :disabled="!form.duration || form.duration <= 0"
+                  @change="(val) => handleProductTimeChange(scope.$index, 'onShelfTime', val)"
+                  style="width: 100%"
+                ></el-time-picker>
+              </template>
+            </el-table-column>
+            <el-table-column label="卡片弹出时间" align="center" prop="cardPopupTime" width="180">
+              <template slot-scope="scope">
+                <el-time-picker
+                  v-model="scope.row.cardPopupTime"
+                  value-format="HH:mm:ss"
+                  format="HH:mm:ss"
+                  placeholder="选择时间"
+                  size="mini"
+                  :disabled="!form.duration || form.duration <= 0"
+                  @change="(val) => handleProductTimeChange(scope.$index, 'cardPopupTime', val)"
+                  style="width: 100%"
+                ></el-time-picker>
+              </template>
+            </el-table-column>
+            <el-table-column label="卡片关闭时间" align="center" prop="cardCloseTime" width="180">
+              <template slot-scope="scope">
+                <el-time-picker
+                  v-model="scope.row.cardCloseTime"
+                  value-format="HH:mm:ss"
+                  format="HH:mm:ss"
+                  placeholder="选择时间"
+                  size="mini"
+                  :disabled="!form.duration || form.duration <= 0"
+                  @change="(val) => handleProductTimeChange(scope.$index, 'cardCloseTime', val)"
+                  style="width: 100%"
+                ></el-time-picker>
+              </template>
+            </el-table-column>
+            <el-table-column label="下架时间" align="center" prop="offShelfTime" width="180">
+              <template slot-scope="scope">
+                <el-time-picker
+                  v-model="scope.row.offShelfTime"
+                  value-format="HH:mm:ss"
+                  format="HH:mm:ss"
+                  placeholder="选择时间"
+                  size="mini"
+                  :disabled="!form.duration || form.duration <= 0"
+                  @change="(val) => handleProductTimeChange(scope.$index, 'offShelfTime', val)"
+                  style="width: 100%"
+                ></el-time-picker>
+              </template>
+            </el-table-column>
             <el-table-column label="操作" align="center" width="100px" fixed="right">
               <template slot-scope="scope">
                 <el-button size="mini" type="text" icon="el-icon-delete"
@@ -342,18 +398,18 @@
             </el-table-column>
           </el-table>
         </el-form-item>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item v-if="form.isProduct === 1" label="商品售卖时间" prop="listingStartTime">
-              <el-input-number v-model="form.listingStartTime" :min="0" label="商品售卖时间"></el-input-number>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item v-if="form.isProduct === 1" label="结束售卖时间" prop="listingStartTime">
-              <el-input-number v-model="form.listingEndTime" :min="0" label="结束售卖时间"></el-input-number>
-            </el-form-item>
-          </el-col>
-        </el-row>
+<!--        <el-row>-->
+<!--          <el-col :span="12">-->
+<!--            <el-form-item v-if="form.isProduct === 1" label="商品售卖时间" prop="listingStartTime">-->
+<!--              <el-input-number v-model="form.listingStartTime" :min="0" label="商品售卖时间"></el-input-number>-->
+<!--            </el-form-item>-->
+<!--          </el-col>-->
+<!--          <el-col :span="12">-->
+<!--            <el-form-item v-if="form.isProduct === 1" label="结束售卖时间" prop="listingStartTime">-->
+<!--              <el-input-number v-model="form.listingEndTime" :min="0" label="结束售卖时间"></el-input-number>-->
+<!--            </el-form-item>-->
+<!--          </el-col>-->
+<!--        </el-row>-->
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button type="primary" @click="submitForm">确 定</el-button>
@@ -800,6 +856,10 @@ export default {
         price: val.price,
         stock: val.stock,
         id: val.productId,  // 添加 id 字段,方便删除操作
+        onShelfTime: val.onShelfTime || '00:00:00',  // 上架时间,默认 00:00:00
+        cardPopupTime: val.cardPopupTime || '00:00:00',  // 卡片弹出时间,默认 00:00:00
+        cardCloseTime: val.cardCloseTime || '00:00:00',  // 卡片关闭时间,默认 00:00:00
+        offShelfTime: val.offShelfTime || '00:00:00',  // 下架时间,默认 00:00:00
       };
       // 检查商品是否已存在
       const exists = this.form.courseProducts.some(item => item.productId === val.productId);
@@ -809,10 +869,166 @@ export default {
       }
 
       // 添加商品到列表
-      this.form.courseProducts.push(val);
+      this.form.courseProducts.push(productData);
       this.$message.success("添加成功");
     },
 
+    /**
+     * 将时间字符串(HH:mm:ss)转换为秒数
+     * @param {String} timeStr 时间字符串,格式:HH:mm:ss
+     * @returns {Number} 总秒数
+     */
+    timeToSeconds(timeStr) {
+      if (!timeStr || timeStr === '') return 0;
+      const parts = timeStr.split(':');
+      if (parts.length !== 3) return 0;
+      const hours = parseInt(parts[0]) || 0;
+      const minutes = parseInt(parts[1]) || 0;
+      const seconds = parseInt(parts[2]) || 0;
+      return hours * 3600 + minutes * 60 + seconds;
+    },
+
+    /**
+     * 处理商品时间字段变化,进行验证
+     * @param {Number} index 商品索引
+     * @param {String} fieldName 字段名称
+     * @param {String} value 时间值(HH:mm:ss格式)
+     */
+    handleProductTimeChange(index, fieldName, value) {
+      if (!this.form.duration || this.form.duration <= 0) {
+        this.$message.warning('请先上传视频并获取视频时长');
+        // 还原为 00:00:00
+        this.$nextTick(() => {
+          const product = this.form.courseProducts[index];
+          if (product) {
+            product[fieldName] = '00:00:00';
+          }
+        });
+        return;
+      }
+
+      const product = this.form.courseProducts[index];
+      if (!product) return;
+
+      const videoDurationSeconds = this.form.duration; // 视频时长(秒)
+      const currentTimeSeconds = this.timeToSeconds(value || '00:00:00');
+
+      // 验证:当前时间不能超过视频长度
+      if (currentTimeSeconds > videoDurationSeconds) {
+        this.$message.error(`${this.getFieldLabel(fieldName)}不能超过视频时长(${this.formatDuration(videoDurationSeconds)})`);
+        // 还原为 00:00:00
+        this.$nextTick(() => {
+          product[fieldName] = '00:00:00';
+        });
+        return;
+      }
+
+      // 验证:关闭时间 - 弹出时间 不能大于视频长度
+      if (fieldName === 'cardCloseTime' && product.cardPopupTime) {
+        const popupSeconds = this.timeToSeconds(product.cardPopupTime);
+        const closeSeconds = this.timeToSeconds(value || '00:00:00');
+        const diffSeconds = closeSeconds - popupSeconds;
+        if (diffSeconds < 0) {
+          this.$message.error('卡片关闭时间不能早于卡片弹出时间');
+          this.$nextTick(() => {
+            product[fieldName] = '00:00:00';
+          });
+          return;
+        }
+        if (diffSeconds > videoDurationSeconds) {
+          this.$message.error('卡片关闭时间与弹出时间的差值不能超过视频时长');
+          this.$nextTick(() => {
+            product[fieldName] = '00:00:00';
+          });
+          return;
+        }
+      }
+
+      // 验证:当修改弹出时间时,需要验证关闭时间
+      if (fieldName === 'cardPopupTime' && product.cardCloseTime) {
+        const popupSeconds = this.timeToSeconds(value || '00:00:00');
+        const closeSeconds = this.timeToSeconds(product.cardCloseTime);
+        const diffSeconds = closeSeconds - popupSeconds;
+        if (diffSeconds < 0) {
+          this.$message.error('卡片弹出时间不能晚于卡片关闭时间');
+          this.$nextTick(() => {
+            product[fieldName] = '00:00:00';
+          });
+          return;
+        }
+        if (diffSeconds > videoDurationSeconds) {
+          this.$message.error('卡片关闭时间与弹出时间的差值不能超过视频时长');
+          this.$nextTick(() => {
+            product[fieldName] = '00:00:00';
+          });
+          return;
+        }
+      }
+
+      // 验证:下架时间 - 上架时间 不能大于视频长度
+      if (fieldName === 'offShelfTime' && product.onShelfTime) {
+        const onShelfSeconds = this.timeToSeconds(product.onShelfTime);
+        const offShelfSeconds = this.timeToSeconds(value || '00:00:00');
+        const diffSeconds = offShelfSeconds - onShelfSeconds;
+        if (diffSeconds < 0) {
+          this.$message.error('下架时间不能早于上架时间');
+          this.$nextTick(() => {
+            product[fieldName] = '00:00:00';
+          });
+          return;
+        }
+        if (diffSeconds > videoDurationSeconds) {
+          this.$message.error('下架时间与上架时间的差值不能超过视频时长');
+          this.$nextTick(() => {
+            product[fieldName] = '00:00:00';
+          });
+          return;
+        }
+      }
+
+      // 验证:当修改上架时间时,需要验证下架时间
+      if (fieldName === 'onShelfTime' && product.offShelfTime) {
+        const onShelfSeconds = this.timeToSeconds(value || '00:00:00');
+        const offShelfSeconds = this.timeToSeconds(product.offShelfTime);
+        const diffSeconds = offShelfSeconds - onShelfSeconds;
+        if (diffSeconds < 0) {
+          this.$message.error('上架时间不能晚于下架时间');
+          this.$nextTick(() => {
+            product[fieldName] = '00:00:00';
+          });
+          return;
+        }
+        if (diffSeconds > videoDurationSeconds) {
+          this.$message.error('下架时间与上架时间的差值不能超过视频时长');
+          this.$nextTick(() => {
+            product[fieldName] = '00:00:00';
+          });
+          return;
+        }
+      }
+
+      // 验证:下架时间不能大于视频长度(已在上面验证)
+      // 验证:卡片关闭时间不能大于视频长度(已在上面验证)
+
+      // 更新值
+      product[fieldName] = value || '00:00:00';
+    },
+
+    /**
+     * 获取字段中文标签
+     * @param {String} fieldName 字段名称
+     * @returns {String} 中文标签
+     */
+    getFieldLabel(fieldName) {
+      const labels = {
+        onShelfTime: '上架时间',
+        cardPopupTime: '卡片弹出时间',
+        cardCloseTime: '卡片关闭时间',
+        offShelfTime: '下架时间'
+      };
+      return labels[fieldName] || fieldName;
+    },
+
     //选择结果
     questionBankResult(val) {
 
@@ -1081,8 +1297,14 @@ export default {
             // 检查 packageJson 是否包含商品数据,如果是则解析为 courseProducts
             const parsedData = JSON.parse(this.form.packageJson);
             if (Array.isArray(parsedData) && parsedData.length > 0 && parsedData[0].hasOwnProperty('productName')) {
-              // 如果是商品数据格式,则设置到 courseProducts
-              this.form.courseProducts = parsedData;
+              // 如果是商品数据格式,则设置到 courseProducts,并确保时间字段有默认值
+              this.form.courseProducts = parsedData.map(product => ({
+                ...product,
+                onShelfTime: product.onShelfTime || '00:00:00',
+                cardPopupTime: product.cardPopupTime || '00:00:00',
+                cardCloseTime: product.cardCloseTime || '00:00:00',
+                offShelfTime: product.offShelfTime || '00:00:00'
+              }));
             } else {
               // 否则按照原有逻辑设置到 packageList
               this.packageList = parsedData;

+ 21 - 0
src/views/hisStore/components/productOrder.vue

@@ -158,6 +158,27 @@
                   {{order.mark}}
                 </span>
             </el-descriptions-item>
+
+            <el-descriptions-item label="销售名称" v-if="order!=null && order.orderType != 0">
+                    <span >
+                      {{ order.companyUserName }}
+                    </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="销售公司" v-if="order!=null && order.orderType != 0">
+                    <span >
+                      {{ order.companyName }}
+                    </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="所属营期" v-if="order!=null && order.orderType == 3">
+                    <span >
+                      {{ order.periodName }}
+                    </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="所属小节" v-if="order!=null && order.orderType == 3">
+                    <span >
+                      {{ order.videoName }}
+                    </span>
+            </el-descriptions-item>
       </el-descriptions>
       <div style="margin: 20px 0px"  v-if="order!=null">
         <span class="font-small">

+ 2 - 0
src/views/hisStore/storeOrder/healthStoreList.vue

@@ -138,6 +138,7 @@
         <el-select v-model="queryParams.orderType" placeholder="请选择订单类型" clearable size="small">
           <el-option label="商城订单" value="0" />
           <el-option label="直播订单" value="2" />
+          <el-option label="点播订单" value="3" />
         </el-select>
       </el-form-item>
       <!--      <el-form-item label="上传凭证" prop="isUpload">
@@ -453,6 +454,7 @@
       <el-table-column align="center" label="订单类型" prop="orderType" width="100">
         <template slot-scope="scope">
           <el-tag v-if="scope.row.orderType == 2" type="warning">直播订单</el-tag>
+          <el-tag v-else-if="scope.row.orderType == 3">点播订单</el-tag>
           <el-tag v-else type="success">商城订单</el-tag>
         </template>
       </el-table-column>

+ 46 - 2
src/views/live/liveData/indexCompany.vue

@@ -60,7 +60,7 @@
       <el-table-column prop="totalAttendanceCount" label="总到课人数(去重)" width="140" align="center">
         <template slot-scope="scope">{{ scope.row.totalAttendanceCount || 0 }}</template>
       </el-table-column>
-      <el-table-column prop="totalCompleteCount" label="总完课人数" width="110" align="center">
+      <el-table-column prop="totalCompleteCount" label="总完课人数(去重)" width="110" align="center">
         <template slot-scope="scope">{{ scope.row.totalCompleteCount || 0 }}</template>
       </el-table-column>
       <el-table-column prop="totalCompleteRate" label="总完课率" width="100" align="center">
@@ -69,7 +69,7 @@
       <el-table-column prop="liveAttendanceCount" label="直播课人数(去重)" width="140" align="center">
         <template slot-scope="scope">{{ scope.row.liveAttendanceCount || 0 }}</template>
       </el-table-column>
-      <el-table-column prop="liveCompleteCount" label="直播完课人数" width="120" align="center">
+      <el-table-column prop="liveCompleteCount" label="直播完课人数(去重)" width="120" align="center">
         <template slot-scope="scope">{{ scope.row.liveCompleteCount || 0 }}</template>
       </el-table-column>
       <el-table-column prop="liveCompleteRate" label="直播完课率" width="110" align="center">
@@ -167,6 +167,28 @@ export default {
     },
     /** 搜索按钮操作 */
     handleQuery() {
+      // 验证公司ID不能为空
+      if (!this.queryParams.companyIds || this.queryParams.companyIds.length === 0) {
+        this.$message.warning('请选择公司');
+        return;
+      }
+
+      // 验证时间范围不能为空
+      if (!this.dateRange || this.dateRange.length !== 2) {
+        this.$message.warning('请选择时间范围');
+        return;
+      }
+
+      // 验证时间范围不能超过31天
+      const startDate = new Date(this.dateRange[0]);
+      const endDate = new Date(this.dateRange[1]);
+      const diffTime = Math.abs(endDate - startDate);
+      const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
+      if (diffDays > 31) {
+        this.$message.warning('时间范围不能超过31天');
+        return;
+      }
+
       this.queryParams.pageNum = 1;
       this.getList();
     },
@@ -195,6 +217,28 @@ export default {
     },
     /** 导出按钮操作 */
     handleExport() {
+      // 验证公司ID不能为空
+      if (!this.queryParams.companyIds || this.queryParams.companyIds.length === 0) {
+        this.$message.warning('请选择公司');
+        return;
+      }
+
+      // 验证时间范围不能为空
+      if (!this.dateRange || this.dateRange.length !== 2) {
+        this.$message.warning('请选择时间范围');
+        return;
+      }
+
+      // 验证时间范围不能超过31天
+      const startDate = new Date(this.dateRange[0]);
+      const endDate = new Date(this.dateRange[1]);
+      const diffTime = Math.abs(endDate - startDate);
+      const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
+      if (diffDays > 31) {
+        this.$message.warning('时间范围不能超过31天');
+        return;
+      }
+
       if (this.dateRange && this.dateRange.length === 2) {
         this.queryParams.startDate = this.dateRange[0];
         this.queryParams.endDate = this.dateRange[1];

+ 8 - 0
src/views/system/config/config.vue

@@ -1546,6 +1546,13 @@
               inactive-color="#13ce66">
             </el-switch>
           </el-form-item>
+          <el-form-item label="是否开启完课倒计时" prop="completionCountdown">
+            <el-switch
+              v-model="form18.completionCountdown"
+              active-color="#13ce66"
+              inactive-color="#ff4949">
+            </el-switch>
+          </el-form-item>
           <div class="line"></div>
           <div style="float:right;margin-right:20px">
             <el-button type="primary" @click="submitForm18">提交</el-button>
@@ -2617,6 +2624,7 @@ export default {
         viewCommentNum: 200,
         roomLinkAllow:true,
         smsDomainName: '', // 初始化为空字符串或其他默认值
+        completionCountdown: false,//是否开启点播完课倒计时
       },
       form19: {},
       form20: {