Bläddra i källkod

Merge remote-tracking branch 'origin/master'

zyp 2 veckor sedan
förälder
incheckning
9c7b167fba
3 ändrade filer med 821 tillägg och 43 borttagningar
  1. 1 6
      src/utils/cos.js
  2. 805 36
      src/views/course/videoResource/index.vue
  3. 15 1
      src/views/system/config/config.vue

+ 1 - 6
src/utils/cos.js

@@ -2,16 +2,10 @@ import COS from 'cos-js-sdk-v5';
 import { Message } from 'element-ui';
 import { getTmpSecretKey } from '@/api/common';
 
-console.log('环境变量:', process.env);
-console.log('NODE_ENV:', process.env.NODE_ENV);
-console.log('VUE_APP_COS_BUCKET:', process.env.VUE_APP_COS_BUCKET);
-console.log('VUE_APP_COS_REGION:', process.env.VUE_APP_COS_REGION);
-
 const config = {
   Bucket: process.env.VUE_APP_COS_BUCKET,
   Region: process.env.VUE_APP_COS_REGION,
 };
-console.log('COS配置:', config);
 
 // 上传到腾讯云cos
 export const uploadObject = async (file,onProgress,type,callBackUp) => {
@@ -29,6 +23,7 @@ export const uploadObject = async (file,onProgress,type,callBackUp) => {
 
         // 初始化
         const cos = new COS({
+            Timeout: 1200 * 1000,
             getAuthorization: (options, callback) => {
                 callback({
                     TmpSecretId: credentials.tmpSecretId,

+ 805 - 36
src/views/course/videoResource/index.vue

@@ -270,13 +270,47 @@
                   <i class="el-icon-delete"></i>
                 </span>
               </span>
-
+              <!-- 改进的上传进度显示 -->
               <div v-if="file.status === 'uploading'" class="el-upload-list__item-progress">
-                <el-progress
+                <!-- <el-progress
                   :percentage="file.percentage"
                   :show-text="false"
                   :width="52"
-                ></el-progress>
+                ></el-progress> -->
+                <div class="dual-upload-progress">
+                  <div class="total-progress">
+                    <el-progress
+                      :percentage="currentUploadProgress.total"
+                      :show-text="true"
+                      :width="52"
+                      :format="() => `${Math.round(currentUploadProgress.total)}%`"
+                    ></el-progress>
+                  </div>
+                  <div class="line-progress-container">
+                    <div class="line-progress-item">
+                      <span class="line-label">线路1:</span>
+                      <el-progress
+                        :percentage="currentUploadProgress.line1"
+                        :show-text="false"
+                        :width="30"
+                        :status="currentUploadProgress.line1Status === 'success' ? 'success' : 
+                                currentUploadProgress.line1Status === 'failed' ? 'exception' : ''"
+                      ></el-progress>
+                      <span class="line-status-text">{{ Math.round(currentUploadProgress.line1) }}%</span>
+                    </div>
+                    <div class="line-progress-item">
+                      <span class="line-label">线路2:</span>
+                      <el-progress
+                        :percentage="currentUploadProgress.line2"
+                        :show-text="false"
+                        :width="30"
+                        :status="currentUploadProgress.line2Status === 'success' ? 'success' : 
+                                currentUploadProgress.line2Status === 'failed' ? 'exception' : ''"
+                      ></el-progress>
+                      <span class="line-status-text">{{ Math.round(currentUploadProgress.line2) }}%</span>
+                    </div>
+                  </div>
+                </div>
               </div>
             </div>
           </el-upload>
@@ -287,8 +321,12 @@
         </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
+        <!-- <el-button @click="cancel">取消</el-button>
+        <el-button type="primary" @click="submitForm">保存</el-button> -->
         <el-button @click="cancel">取消</el-button>
-        <el-button type="primary" @click="submitForm">保存</el-button>
+        <el-button type="primary" @click="submitForm" :disabled="isUploading">
+          {{ isUploading ? '上传中...' : '保存' }}
+        </el-button>
       </div>
     </minimizable-dialog>
 
@@ -371,10 +409,50 @@
             {{ formatDuration(scope.row.duration) }}
           </template>
         </el-table-column>
-        <el-table-column label="上传进度" align="center" width="120">
+        <!-- 改进的上传进度列 -->
+        <!-- <el-table-column label="上传进度" align="center" width="120">
           <template slot-scope="scope">
             <el-progress :percentage="scope.row.progress" :status="scope.row.progress === 100 ? 'success' : undefined"></el-progress>
           </template>
+        </el-table-column> -->
+        <el-table-column label="上传进度" align="center" width="200">
+          <template slot-scope="scope">
+            <div class="batch-upload-progress">
+              <div class="total-progress-row">
+                <span class="progress-label">总进度:</span>
+                <el-progress 
+                  :percentage="scope.row.progress || 0" 
+                  :status="getProgressStatus(scope.row)"
+                  :show-text="true"
+                  :format="() => `${Math.round(scope.row.progress || 0)}%`"
+                ></el-progress>
+              </div>
+              <div v-if="scope.row.uploadDetails" class="line-progress-rows">
+                <div class="line-progress-row">
+                  <span class="line-label">线路1:</span>
+                  <el-progress 
+                    :percentage="scope.row.uploadDetails.line1 || 0"
+                    :status="scope.row.uploadDetails.line1Status === 'success' ? 'success' : 
+                            scope.row.uploadDetails.line1Status === 'failed' ? 'exception' : 'warning'"
+                    :show-text="false"
+                    style="width: 60px;"
+                  ></el-progress>
+                  <span class="line-percentage">{{ Math.round(scope.row.uploadDetails.line1 || 0) }}%</span>
+                </div>
+                <div class="line-progress-row">
+                  <span class="line-label">线路2:</span>
+                  <el-progress 
+                    :percentage="scope.row.uploadDetails.line2 || 0"
+                    :status="scope.row.uploadDetails.line2Status === 'success' ? 'success' : 
+                            scope.row.uploadDetails.line2Status === 'failed' ? 'exception' : 'warning'"
+                    :show-text="false"
+                    style="width: 60px;"
+                  ></el-progress>
+                  <span class="line-percentage">{{ Math.round(scope.row.uploadDetails.line2 || 0) }}%</span>
+                </div>
+              </div>
+            </div>
+          </template>
         </el-table-column>
         <el-table-column label="操作" align="center" width="150">
           <template slot-scope="scope">
@@ -388,6 +466,13 @@
               type="text"
               icon="el-icon-delete"
               @click="handleDeleteVideo(scope.row)">删除</el-button>
+              <el-button
+              v-if="scope.row.progress < 100 && scope.row.uploadStatus === 'failed'"
+              size="mini"
+              type="text"
+              icon="el-icon-refresh"
+              @click="retryBatchUpload(scope.row)"
+              style="color: #E6A23C;">重试</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -700,7 +785,16 @@ export default {
         videoUrl: null,
         typeId: null,
         typeSubId: null,
-        projectIds: []
+        projectIds: [],
+        // 新增上传状态字段
+        uploadStatus: 'pending', // pending, uploading, success, failed
+        uploadProgress: {
+          total: 0,
+          line1: 0,
+          line2: 0,
+          line1Status: 'pending', // pending, uploading, success, failed
+          line2Status: 'pending'
+        }
       },
       // 表单校验
       rules: {
@@ -783,6 +877,17 @@ export default {
       },
       // 是否存在最小化窗口
       hasMinimizableDialog: false,
+      // 新增上传相关状态
+      isUploading: false,
+      currentUploadProgress: {
+        total: 0,
+        line1: 0,
+        line2: 0,
+        line1Status: 'pending',
+        line2Status: 'pending'
+      },
+      // 上传任务队列
+      uploadQueue: []
     }
   },
   watch: {
@@ -1010,6 +1115,7 @@ export default {
     handleVideoPreview(url) {
       this.videoPreviewVisible = true;
       this.videoPreviewUrl = url || this.form.videoUrl;
+      console.log("--------------------",this.videoPreviewUrl)
     },
     /** 格式化视频时长 */
     formatDuration(seconds) {
@@ -1116,66 +1222,222 @@ export default {
     },
     //上传腾讯云Pcdn
     async uploadVideoToTxPcdn(file, form, onProgress) {
+      // try {
+      //   const data = await uploadObject(file, (progress) => {
+      //     const progressEvent = {
+      //       percent: Math.floor(progress.percent * 100 / 2), // COS SDK 百分比是 0-1,el-upload 需要 0-100
+      //       loaded: progress.loaded,
+      //       total: progress.total,
+      //       lengthComputable: true // 文件上传通常总大小可知
+      //     };
+      //     onProgress(progressEvent);
+      //   }, 1);
+
+      //   let line_1 = `${process.env.VUE_APP_VIDEO_LINE_1}${data.urlPath}`;
+
+      //   form.fileKey = data.urlPath.substring(1);
+      //   form.videoUrl = line_1;
+      //   form.line1 = line_1;
+
+      //   this.$message.success("线路一上传成功");
+      // } catch (error) {
+      //   this.$message.error("线路一上传失败");
+      // }
       try {
+        // 更新线路1状态为上传中
+        this.updateUploadProgress('line1Status', 'uploading');
+        
         const data = await uploadObject(file, (progress) => {
+          const progressPercent = Math.floor(progress.percent * 100);
+          this.updateUploadProgress('line1', progressPercent);
+          
           const progressEvent = {
-            percent: Math.floor(progress.percent * 100 / 2), // COS SDK 百分比是 0-1,el-upload 需要 0-100
+            percent: progressPercent,
             loaded: progress.loaded,
             total: progress.total,
-            lengthComputable: true // 文件上传通常总大小可知
+            lengthComputable: true
           };
           onProgress(progressEvent);
         }, 1);
-
+        
         let line_1 = `${process.env.VUE_APP_VIDEO_LINE_1}${data.urlPath}`;
-
         form.fileKey = data.urlPath.substring(1);
         form.videoUrl = line_1;
         form.line1 = line_1;
-
+        
+        // 更新线路1状态为成功
+        this.updateUploadProgress('line1Status', 'success');
+        this.updateUploadProgress('line1', 100);
+        
         this.$message.success("线路一上传成功");
+        return { success: true, url: line_1 };
       } catch (error) {
+        // 更新线路1状态为失败
+        this.updateUploadProgress('line1Status', 'failed');
         this.$message.error("线路一上传失败");
+        return { success: false, error: error.message };
       }
     },
     //上传华为云Obs
     async uploadVideoToHwObs(file, form, onProgress) {
+      // try {
+      //   const data = await uploadToOBS(file, (progress) => {
+      //     const progressEvent = {
+      //       percent: Math.floor(progress / 2) + 50,
+      //       loaded: progress,
+      //       total: progress,
+      //       lengthComputable: true // 文件上传通常总大小可知
+      //     };
+      //     onProgress(progressEvent);
+      //   }, 1);
+
+      //   form.line2 = `${process.env.VUE_APP_VIDEO_LINE_2}/${data.urlPath}`;
+      //   this.$message.success("线路二上传成功");
+      // } catch (error) {
+      //   this.$message.error("线路二上传失败");
+      // }
       try {
+        // 更新线路2状态为上传中
+        this.updateUploadProgress('line2Status', 'uploading');
+        
         const data = await uploadToOBS(file, (progress) => {
+          const progressPercent = Math.floor(progress);
+          this.updateUploadProgress('line2', progressPercent);
+          
           const progressEvent = {
-            percent: Math.floor(progress / 2) + 50,
+            percent: progressPercent,
             loaded: progress,
             total: progress,
-            lengthComputable: true // 文件上传通常总大小可知
+            lengthComputable: true
           };
           onProgress(progressEvent);
         }, 1);
-
+        
         form.line2 = `${process.env.VUE_APP_VIDEO_LINE_2}/${data.urlPath}`;
+        
+        // 更新线路2状态为成功
+        this.updateUploadProgress('line2Status', 'success');
+        this.updateUploadProgress('line2', 100);
+        
         this.$message.success("线路二上传成功");
+        return { success: true, url: form.line2 };
       } catch (error) {
+        // 更新线路2状态为失败
+        this.updateUploadProgress('line2Status', 'failed');
         this.$message.error("线路二上传失败");
+        return { success: false, error: error.message };
+      }
+    },
+    // 更新上传进度的辅助方法
+    updateUploadProgress(key, value) {
+      this.currentUploadProgress[key] = value;
+      
+      // 计算总进度:只有两个线路都成功才算100%
+      if (this.currentUploadProgress.line1Status === 'success' && 
+          this.currentUploadProgress.line2Status === 'success') {
+        this.currentUploadProgress.total = 100;
+      } else if (this.currentUploadProgress.line1Status === 'failed' || 
+                 this.currentUploadProgress.line2Status === 'failed') {
+        // 如果任一线路失败,总进度保持当前状态
+        this.currentUploadProgress.total = Math.min(
+          (this.currentUploadProgress.line1 + this.currentUploadProgress.line2) / 2,
+          99 // 失败时最多99%
+        );
+      } else {
+        // 正常上传中,计算平均进度
+        this.currentUploadProgress.total = Math.min(
+          (this.currentUploadProgress.line1 + this.currentUploadProgress.line2) / 2,
+          99 // 上传中最多99%,只有都成功才100%
+        );
       }
     },
     // 上传视频
     async videoUpload(options) {
-      this.add = true
-      const file = options.file;
-      this.getMediaDuration(file)
+      // this.add = true
+      // const file = options.file;
+      // this.getMediaDuration(file)
 
-      // 获取第一帧图片
-      await this.getFirstThumbnail(file, this.form);
+      // // 获取第一帧图片
+      // await this.getFirstThumbnail(file, this.form);
 
-      // 上传腾讯云pcdn
-      await this.uploadVideoToTxPcdn(file, this.form, options.onProgress);
+      // // 上传腾讯云pcdn
+      // await this.uploadVideoToTxPcdn(file, this.form, options.onProgress);
 
-      // 上传华为obs
-      await this.uploadVideoToHwObs(file, this.form, options.onProgress);
+      // // 上传华为obs
+      // await this.uploadVideoToHwObs(file, this.form, options.onProgress);
 
-      this.form.fileName = file.name;
-      this.form.fileSize = file.size;
+      // this.form.fileName = file.name;
+      // this.form.fileSize = file.size;
 
-      this.add = false
+      // this.add = false
+      this.isUploading = true;
+      this.form.uploadStatus = 'uploading';
+      
+      const file = options.file;
+      this.getMediaDuration(file);
+      
+      // 重置进度
+      this.currentUploadProgress = {
+        total: 0,
+        line1: 0,
+        line2: 0,
+        line1Status: 'pending',
+        line2Status: 'pending'
+      };
+
+      try {
+        // 获取第一帧图片
+        await this.getFirstThumbnail(file, this.form);
+
+        // 并行上传到两个服务器
+        const [line1Result, line2Result] = await Promise.allSettled([
+          this.uploadVideoToTxPcdn(file, this.form, options.onProgress),
+          this.uploadVideoToHwObs(file, this.form, options.onProgress)
+        ]);
+
+        // 检查上传结果
+        const line1Success = line1Result.status === 'fulfilled' && line1Result.value.success;
+        const line2Success = line2Result.status === 'fulfilled' && line2Result.value.success;
+
+        if (line1Success && line2Success) {
+          // 两个都成功
+          this.form.uploadStatus = 'success';
+          this.form.uploadProgress = {
+            total: 100,
+            line1: 100,
+            line2: 100,
+            line1Status: 'success',
+            line2Status: 'success'
+          };
+          this.currentUploadProgress.total = 100;
+          this.$message.success("视频上传完成!两个线路都上传成功");
+        } else {
+          // 至少有一个失败
+          this.form.uploadStatus = 'failed';
+          this.form.uploadProgress = {
+            total: this.currentUploadProgress.total,
+            line1: this.currentUploadProgress.line1,
+            line2: this.currentUploadProgress.line2,
+            line1Status: this.currentUploadProgress.line1Status,
+            line2Status: this.currentUploadProgress.line2Status
+          };
+          
+          const failedLines = [];
+          if (!line1Success) failedLines.push('线路1');
+          if (!line2Success) failedLines.push('线路2');
+          
+          this.$message.error(`视频上传失败!${failedLines.join('、')} 上传失败,请重试`);
+        }
+
+        this.form.fileName = file.name;
+        this.form.fileSize = file.size;
+        
+      } catch (error) {
+        this.form.uploadStatus = 'failed';
+        this.$message.error("视频上传过程中发生错误,请重试");
+      } finally {
+        this.isUploading = false;
+      }
     },
     // 获取媒体文件时长
     getMediaDuration(file) {
@@ -1337,7 +1599,14 @@ export default {
         typeId: this.batchUploadForm.typeId, // 使用选择的分类
         typeSubId: this.batchUploadForm.typeSubId, // 使用选择的子分类
         projectIds: this.batchUploadForm.projectIds, // 使用选择的项目
-        progress: 0, // 上传进度
+        progress: 0, //  总进度
+        uploadStatus: 'uploading',
+        uploadDetails: {
+          line1: 0,
+          line2: 0,
+          line1Status: 'pending',
+          line2Status: 'pending'
+        },
         file: file // 保存文件引用
       };
 
@@ -1362,15 +1631,53 @@ export default {
         // 获取封面
         await this.getFirstThumbnail(file, tempVideo);
 
-        // 上传到第一个服务器
-        await this.uploadVideoToTxPcdn(file, tempVideo, (event) => {
-            tempVideo.progress = event.percent
-        });
-
-        // 上传到第二个服务器
-        await this.uploadVideoToHwObs(file, tempVideo, (event) => {
-          tempVideo.progress = event.percent
-        });
+        // // 上传到第一个服务器
+        // await this.uploadVideoToTxPcdn(file, tempVideo, (event) => {
+        //     tempVideo.progress = event.percent
+        // });
+
+        // // 上传到第二个服务器
+        // await this.uploadVideoToHwObs(file, tempVideo, (event) => {
+        //   tempVideo.progress = event.percent
+        // });
+        // 并行上传到两个服务器
+        const [line1Result, line2Result] = await Promise.allSettled([
+          this.uploadVideoToTxPcdnBatch(file, tempVideo),
+          this.uploadVideoToHwObsBatch(file, tempVideo)
+        ]);
+
+        // 检查上传结果
+        const line1Success = line1Result.status === 'fulfilled' && line1Result.value.success;
+        const line2Success = line2Result.status === 'fulfilled' && line2Result.value.success;
+        if (line1Success && line2Success) {
+          // 两个都成功,更新进度为100%
+          const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
+          if (index !== -1) {
+            this.videoList[index].progress = 100;
+            this.videoList[index].uploadStatus = 'success';
+            this.videoList[index].uploadDetails.line1Status = 'success';
+            this.videoList[index].uploadDetails.line2Status = 'success';
+            this.videoList[index].uploadDetails.line1 = 100;
+            this.videoList[index].uploadDetails.line2 = 100;
+          }
+          this.$message.success(`文件 ${file.name} 上传成功`);
+        } else {
+          // 至少有一个失败
+          const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
+          if (index !== -1) {
+            this.videoList[index].uploadStatus = 'failed';
+            this.videoList[index].uploadDetails.line1Status = line1Success ? 'success' : 'failed';
+            this.videoList[index].uploadDetails.line2Status = line2Success ? 'success' : 'failed';
+            // 失败时保持当前进度,不设为100%
+            this.updateBatchProgress(index);
+          }
+          
+          const failedLines = [];
+          if (!line1Success) failedLines.push('线路1');
+          if (!line2Success) failedLines.push('线路2');
+          
+          this.$message.error(`文件 ${file.name} 在 ${failedLines.join('、')} 上传失败`);
+        }
 
       } catch (error) {
         this.$message.error(`文件 ${file.name} 上传失败: ${error.message || '未知错误'}`);
@@ -1615,6 +1922,350 @@ export default {
       this.videoPreviewVisible = false;
       done();
     },
+    // 批量上传 - 腾讯云
+    async uploadVideoToTxPcdnBatch(file, tempVideo) {
+      console.log("--------------",JSON.stringify(tempVideo))
+      try {
+        const data = await uploadObject(file, (progress) => {
+          const progressPercent = Math.floor(progress.percent * 100);
+          const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
+          if (index !== -1) {
+            this.videoList[index].uploadDetails.line1 = progressPercent;
+            this.videoList[index].uploadDetails.line1Status = 'uploading';
+            // 更新总进度
+            this.updateBatchProgress(index);
+          }
+        }, 1);
+
+        let line_1 = `${process.env.VUE_APP_VIDEO_LINE_1}${data.urlPath}`;
+        tempVideo.fileKey = data.urlPath.substring(1);
+        tempVideo.videoUrl = line_1;
+        tempVideo.line1 = line_1;
+
+        return { success: true, url: line_1 };
+      } catch (error) {
+        return { success: false, error: error.message };
+      }
+    },
+
+    // 批量上传 - 华为云
+    async uploadVideoToHwObsBatch(file, tempVideo) {
+      try {
+        const data = await uploadToOBS(file, (progress) => {
+          const progressPercent = Math.floor(progress);
+          const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
+          if (index !== -1) {
+            this.videoList[index].uploadDetails.line2 = progressPercent;
+            this.videoList[index].uploadDetails.line2Status = 'uploading';
+            // 更新总进度
+            this.updateBatchProgress(index);
+          }
+        }, 1);
+
+        tempVideo.line2 = `${process.env.VUE_APP_VIDEO_LINE_2}/${data.urlPath}`;
+        return { success: true, url: tempVideo.line2 };
+      } catch (error) {
+        return { success: false, error: error.message };
+      }
+    },
+
+    // 更新批量上传的总进度
+    updateBatchProgress(index) {
+      if (index >= 0 && index < this.videoList.length) {
+        const item = this.videoList[index];
+        const line1Progress = item.uploadDetails.line1 || 0;
+        const line2Progress = item.uploadDetails.line2 || 0;
+        
+        // 只有两个线路都成功才算100%
+        if (item.uploadDetails.line1Status === 'success' && 
+            item.uploadDetails.line2Status === 'success') {
+          item.progress = 100;
+        } else if (item.uploadDetails.line1Status === 'failed' || 
+                   item.uploadDetails.line2Status === 'failed') {
+          // 如果任一线路失败,总进度保持当前状态,不超过99%
+          item.progress = Math.min((line1Progress + line2Progress) / 2, 99);
+        } else {
+          // 正常上传中,计算平均进度,不超过99%
+          item.progress = Math.min((line1Progress + line2Progress) / 2, 99);
+        }
+      }
+    },
+
+    // 重试批量上传
+    async retryBatchUpload(row) {
+      // const index = this.videoList.findIndex(item => item.tempId === row.tempId);
+      // if (index === -1) return;
+
+      // const {line1, line2,line1Status,line2Status} = this.videoList[index].uploadDetails
+
+      
+
+      // // 重置状态
+      // this.videoList[index].uploadStatus = 'uploading';
+      // // this.videoList[index].progress = 0;
+      // this.videoList[index].uploadDetails = {
+      //   line1: line1 == 100?line1:0,
+      //   line2: line2 == 100?line2:0,
+      //   line1Status: line1Status =='success'?line1Status:'pending',
+      //   line2Status: line2Status =='success'?line2Status:'pending'
+      // };
+
+      // const tempVideo = this.videoList[index];
+      
+      // try {
+      //   // 重新上传
+      //   // const [line1Result, line2Result] = await Promise.allSettled([
+      //   //   this.uploadVideoToTxPcdnBatch(tempVideo.file, tempVideo),
+      //   //   // this.uploadVideoToHwObsBatch(tempVideo.file, tempVideo)
+      //   // ]);
+      //   if (line1 !== 100) {
+      //     const line1Result = await this.uploadVideoToTxPcdnBatch(tempVideo.file, tempVideo)
+      //   }
+
+      //   if (line2 !== 100) {
+      //     const line2Result = await this.uploadVideoToHwObsBatch(tempVideo.file, tempVideo)
+      //   }
+
+      //   const line1Success = line1Result.status === 'fulfilled' && line1Result.value.success;
+      //   const line2Success = line2Result.status === 'fulfilled' && line2Result.value.success;
+
+      //   if ( line1 == 100 || line1Success) {
+      //     this.videoList[index].uploadDetails.line1Status = 'success';
+      //     this.videoList[index].uploadDetails.line1 = 100;
+      //     this.$message.success(`文件 ${tempVideo.fileName} 重试上传成功`);
+      //   } else {
+      //     this.videoList[index].uploadDetails.line1Status = line1Success ? 'success' : 'failed';
+      //     this.$message.error(`文件 ${tempVideo.fileName} 重试上传失败`);
+      //   }
+
+      //   if (line2 == 100 || line2Success) {
+      //     this.videoList[index].uploadDetails.line2Status = 'success';
+      //     this.videoList[index].uploadDetails.line2 = 100;
+      //     this.$message.success(`文件 ${tempVideo.fileName} 重试上传成功`);
+      //   } else {
+      //     this.videoList[index].uploadDetails.line2Status = line2Success ? 'success' : 'failed';
+      //     this.$message.error(`文件 ${tempVideo.fileName} 重试上传失败`);
+      //   }
+
+      //   // if (line1Success && line2Success) {
+      //   //   this.videoList[index].progress = 100;
+      //   //   this.videoList[index].uploadStatus = 'success';
+      //   //   this.videoList[index].uploadDetails.line1Status = 'success';
+      //   //   this.videoList[index].uploadDetails.line2Status = 'success';
+      //   //   this.videoList[index].uploadDetails.line1 = 100;
+      //   //   this.videoList[index].uploadDetails.line2 = 100;
+      //   //   this.$message.success(`文件 ${tempVideo.fileName} 重试上传成功`);
+      //   // } else {
+      //   //   this.videoList[index].uploadStatus = 'failed';
+      //   //   this.videoList[index].uploadDetails.line1Status = line1Success ? 'success' : 'failed';
+      //   //   this.videoList[index].uploadDetails.line2Status = line2Success ? 'success' : 'failed';
+      //   //   this.$message.error(`文件 ${tempVideo.fileName} 重试上传失败`);
+      //   // }
+      // } catch (error) {
+      //   this.videoList[index].uploadStatus = 'failed';
+      //   this.$message.error(`文件 ${tempVideo.fileName} 重试上传失败`);
+      // }
+      const index = this.videoList.findIndex(item => item.tempId === row.tempId);
+      if (index === -1) return;
+
+      const tempVideo = this.videoList[index];
+      const uploadDetails = tempVideo.uploadDetails || {};
+      
+      // 检查哪些线路需要重试
+      const needRetryLine1 = uploadDetails.line1Status === 'failed' || uploadDetails.line1Status === 'pending';
+      const needRetryLine2 = uploadDetails.line2Status === 'failed' || uploadDetails.line2Status === 'pending';
+      
+      if (!needRetryLine1 && !needRetryLine2) {
+        this.$message.info('所有线路都已上传成功,无需重试');
+        return;
+      }
+
+      // 更新整体状态为上传中
+      this.videoList[index].uploadStatus = 'uploading';
+      
+      // 只重置需要重试的线路状态
+      if (needRetryLine1) {
+        this.videoList[index].uploadDetails.line1 = 0;
+        this.videoList[index].uploadDetails.line1Status = 'pending';
+      }
+      if (needRetryLine2) {
+        this.videoList[index].uploadDetails.line2 = 0;
+        this.videoList[index].uploadDetails.line2Status = 'pending';
+      }
+
+      try {
+        const uploadPromises = [];
+        
+        // 根据需要重试的线路创建上传任务
+        if (needRetryLine1) {
+          uploadPromises.push(
+            this.uploadVideoToTxPcdnBatch(tempVideo.file, tempVideo)
+              .then(result => ({ line: 'line1', result }))
+              .catch(error => ({ line: 'line1', result: { success: false, error: error.message } }))
+          );
+        } else {
+          // 如果线路1不需要重试,创建一个已成功的Promise
+          uploadPromises.push(Promise.resolve({ line: 'line1', result: { success: true } }));
+        }
+        
+        if (needRetryLine2) {
+          uploadPromises.push(
+            this.uploadVideoToHwObsBatch(tempVideo.file, tempVideo)
+              .then(result => ({ line: 'line2', result }))
+              .catch(error => ({ line: 'line2', result: { success: false, error: error.message } }))
+          );
+        } else {
+          // 如果线路2不需要重试,创建一个已成功的Promise
+          uploadPromises.push(Promise.resolve({ line: 'line2', result: { success: true } }));
+        }
+
+        // 等待所有上传任务完成
+        const results = await Promise.all(uploadPromises);
+        
+        // 处理结果
+        let line1Success = true;
+        let line2Success = true;
+        let retryMessages = [];
+        
+        results.forEach(({ line, result }) => {
+          if (line === 'line1') {
+            line1Success = result.success;
+            if (needRetryLine1) {
+              if (result.success) {
+                this.videoList[index].uploadDetails.line1Status = 'success';
+                this.videoList[index].uploadDetails.line1 = 100;
+                retryMessages.push('线路1重试成功');
+              } else {
+                this.videoList[index].uploadDetails.line1Status = 'failed';
+                retryMessages.push('线路1重试失败');
+              }
+            }
+          } else if (line === 'line2') {
+            line2Success = result.success;
+            if (needRetryLine2) {
+              if (result.success) {
+                this.videoList[index].uploadDetails.line2Status = 'success';
+                this.videoList[index].uploadDetails.line2 = 100;
+                retryMessages.push('线路2重试成功');
+              } else {
+                this.videoList[index].uploadDetails.line2Status = 'failed';
+                retryMessages.push('线路2重试失败');
+              }
+            }
+          }
+        });
+
+        // 更新总体状态和进度
+        if (line1Success && line2Success) {
+          this.videoList[index].progress = 100;
+          this.videoList[index].uploadStatus = 'success';
+          this.$message.success(`文件 ${tempVideo.fileName} 重试完成:${retryMessages.join(',')}`);
+        } else {
+          this.videoList[index].uploadStatus = 'failed';
+          // 重新计算进度
+          this.updateBatchProgress(index);
+          this.$message.error(`文件 ${tempVideo.fileName} 重试完成:${retryMessages.join(',')}`);
+        }
+        
+      } catch (error) {
+        this.videoList[index].uploadStatus = 'failed';
+        this.$message.error(`文件 ${tempVideo.fileName} 重试过程中发生错误:${error.message || '未知错误'}`);
+      }
+    },
+
+    // 重试单个上传
+    async retryUpload(row) {
+      this.$confirm('确认要重新上传该视频吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(async () => {
+        // 这里需要重新触发上传,但由于原始文件可能已经不存在
+        // 建议用户重新选择文件上传
+        this.$message.info('请重新选择文件进行上传');
+        this.handleUpdate(row);
+      }).catch(() => {});
+    },
+    // 获取上传状态图标
+    getUploadStatusIcon(status) {
+      switch (status) {
+        case 'success':
+          return 'el-icon-success';
+        case 'failed':
+          return 'el-icon-error';
+        case 'uploading':
+          return 'el-icon-loading';
+        default:
+          return 'el-icon-time';
+      }
+    },
+
+    // 获取上传状态颜色
+    getUploadStatusColor(status) {
+      switch (status) {
+        case 'success':
+          return '#67C23A';
+        case 'failed':
+          return '#F56C6C';
+        case 'uploading':
+          return '#409EFF';
+        default:
+          return '#909399';
+      }
+    },
+
+    // 获取上传状态文本
+    getUploadStatusText(status) {
+      switch (status) {
+        case 'success':
+          return '上传成功';
+        case 'failed':
+          return '上传失败';
+        case 'uploading':
+          return '上传中';
+        default:
+          return '待上传';
+      }
+    },
+
+    // 获取线路状态图标
+    getLineStatusIcon(status) {
+      switch (status) {
+        case 'success':
+          return 'el-icon-check';
+        case 'failed':
+          return 'el-icon-close';
+        case 'uploading':
+          return 'el-icon-loading';
+        default:
+          return 'el-icon-minus';
+      }
+    },
+
+    // 获取线路状态颜色
+    getLineStatusColor(status) {
+      switch (status) {
+        case 'success':
+          return '#67C23A';
+        case 'failed':
+          return '#F56C6C';
+        case 'uploading':
+          return '#409EFF';
+        default:
+          return '#C0C4CC';
+      }
+    },
+
+    // 获取进度条状态
+    getProgressStatus(row) {
+      if (row.progress === 100 && row.uploadStatus === 'success') {
+        return 'success';
+      } else if (row.uploadStatus === 'failed') {
+        return 'exception';
+      }
+      return '';
+    },
+
   }
 }
 </script>
@@ -1630,6 +2281,124 @@ export default {
   margin-left: 5px;
 }
 
+/* 上传状态样式 */
+.upload-status-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 4px;
+}
+
+.status-indicator {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.status-text {
+  font-size: 12px;
+}
+
+.upload-details {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+  font-size: 11px;
+}
+
+.line-status {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.line-label {
+  min-width: 35px;
+  color: #606266;
+}
+
+.line-progress {
+  color: #909399;
+}
+
+/* 双线上传进度样式 */
+.dual-upload-progress {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 4px;
+}
+
+.total-progress {
+  margin-bottom: 4px;
+}
+
+.line-progress-container {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+  width: 100%;
+}
+
+.line-progress-item {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 10px;
+}
+
+.line-label {
+  min-width: 30px;
+  color: #606266;
+}
+
+.line-status-text {
+  min-width: 25px;
+  color: #909399;
+  font-size: 10px;
+}
+
+/* 批量上传进度样式 */
+.batch-upload-progress {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.total-progress-row {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  white-space: nowrap; /* 防止换行 */
+  min-width: 0; /* 允许flex项目收缩 */
+}
+
+.progress-label {
+  font-size: 12px;
+  color: #606266;
+  min-width: 40px;
+
+}
+
+.line-progress-rows {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+}
+
+.line-progress-row {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 11px;
+}
+
+.line-percentage {
+  min-width: 30px;
+  color: #909399;
+  font-size: 11px;
+}
+
 ::v-deep .upload-icon {
   font-size: 28px;
   color: #8c939d;

+ 15 - 1
src/views/system/config/config.vue

@@ -582,6 +582,17 @@
               />
             </el-select>
         </el-form-item>
+        <!-- <el-form-item   label="推送erp的商品类型" v-if="form13.erpOpen == 1">
+          <el-select filterable v-model="form13.productType" placeholder="请选商品类型" multiple clearable size="small"
+              >
+              <el-option
+                  v-for="item in productTypeOptions"
+                  :key="item.dictValue"
+                  :label="item.dictLabel"
+                  :value="item.dictValue"
+              />
+            </el-select>
+        </el-form-item> -->
         <el-form-item   label="erpAppKey" v-if="form13.erpOpen == 1 && form13.erpType == 1 " prop="erpAppKey">
             <el-input   v-model="form13.erpAppKey"  label="请输入erpAppKey"></el-input>
         </el-form-item>
@@ -686,6 +697,7 @@ export default {
   },
   data() {
     return {
+      // productTypeOptions:[],
       companyOptions:[],
       uploadUrl:process.env.VUE_APP_BASE_API+"/common/uploadOSS",
       videoAccept:"video/*",
@@ -904,7 +916,9 @@ export default {
              if(response.data.configValue != null) {
                this.form13 =JSON.parse(response.data.configValue);
              }
-
+            //  this.getDicts("store_product_type").then((response) => {
+            //   this.productTypeOptions = response.data;
+            // });
           }
         });
      },