Browse Source

视频上传优化

ct 1 day ago
parent
commit
928a701c9b
3 changed files with 751 additions and 60 deletions
  1. 10 14
      .env.development
  2. 1 0
      src/utils/cos.js
  3. 740 46
      src/views/course/videoResource/index.vue

+ 10 - 14
.env.development

@@ -1,15 +1,15 @@
 # 页面标题
-VUE_APP_TITLE =小访客管理系统
+VUE_APP_TITLE =金牛明医互联网医院管理系统
 # 首页菜单标题
-VUE_APP_TITLE_INDEX =小访客
+VUE_APP_TITLE_INDEX =金牛明医
 # 公司名称
-VUE_APP_COMPANY_NAME =西安小访客网络科技有限公司
+VUE_APP_COMPANY_NAME =成都金牛明医云联互联网医院有限公司
 # ICP备案号
-VUE_APP_ICP_RECORD =陕ICP备2025066365号-3
+VUE_APP_ICP_RECORD =蜀ICP备2024052643号
 # ICP网站访问地址
 VUE_APP_ICP_URL =https://beian.miit.gov.cn
 # 网站LOG
-VUE_APP_LOG_URL =@/assets/logo/xfk.png
+VUE_APP_LOG_URL =@/assets/logo/jnmy.png
 # 存储桶配置
 VUE_APP_OBS_ACCESS_KEY_ID = K2UTJGIN7UTZJR2XMXYG
 # 存储桶配置
@@ -17,18 +17,18 @@ VUE_APP_OBS_SECRET_ACCESS_KEY = sbyeNJLbcYmH6copxeFP9pAoksM4NIT9Zw4x0SRX
 # 存储桶配置
 VUE_APP_OBS_SERVER = https://obs.cn-north-4.myhuaweicloud.com
 # 存储桶配置
-VUE_APP_OBS_BUCKET = xfk-hw079058881
+VUE_APP_OBS_BUCKET = jnmy-hw079058881
 # 存储桶配置
-VUE_APP_COS_BUCKET = xfk-1323137866
+VUE_APP_COS_BUCKET = jnmy-1323137866
 # 存储桶配置
 VUE_APP_COS_REGION = ap-chongqing
 # 线路一地址
-VUE_APP_VIDEO_LINE_1 = https://xfktcpv.ylrzcloud.com
+VUE_APP_VIDEO_LINE_1 = https://jnmytcpv.ylrzcloud.com
 # 线路二地址
-VUE_APP_VIDEO_LINE_2 = https://xfkobs.ylrztop.com
+VUE_APP_VIDEO_LINE_2 = https://jnmyobs.ylrztop.com
 
 # 开发环境配置
-ENV = 'production'
+ENV = 'development'
 
 # FS管理系统/开发环境
 VUE_APP_BASE_API = '/prod-api'
@@ -38,7 +38,3 @@ VUE_APP_COURSE_DEFAULT = 1
 
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true
-# 患者信息
-VUE_APP_PATIENT_INFO = '客户信息'
-# 添加病人
-VUE_APP_ADD_PATIENT = '添加信息'

+ 1 - 0
src/utils/cos.js

@@ -29,6 +29,7 @@ export const uploadObject = async (file,onProgress,type,callBackUp) => {
 
         // 初始化
         const cos = new COS({
+            Timeout: 1200 * 1000,
             getAuthorization: (options, callback) => {
                 callback({
                     TmpSecretId: credentials.tmpSecretId,

+ 740 - 46
src/views/course/videoResource/index.vue

@@ -277,11 +277,40 @@
               </span>
 
               <div v-if="file.status === 'uploading'" class="el-upload-list__item-progress">
-                <el-progress
-                  :percentage="file.percentage"
-                  :show-text="false"
-                  :width="52"
-                ></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>
@@ -293,7 +322,9 @@
       </el-form>
       <div slot="footer" class="dialog-footer">
         <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>
 
@@ -376,10 +407,45 @@
             {{ formatDuration(scope.row.duration) }}
           </template>
         </el-table-column>
-        <el-table-column label="上传进度" align="center" width="120">
+        <el-table-column label="上传进度" align="center" width="200">
           <template slot-scope="scope">
-            <el-progress :percentage="scope.row.progress" :status="scope.row.progress === 100 ? 'success' : undefined"></el-progress>
+            <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">
@@ -393,6 +459,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>
@@ -706,7 +779,16 @@ export default {
         typeId: null,
         typeSubId: null,
         projectIds: [],
-        sort: null
+        sort: null,
+        // 新增上传状态字段
+        uploadStatus: 'pending', // pending, uploading, success, failed
+        uploadProgress: {
+          total: 0,
+          line1: 0,
+          line2: 0,
+          line1Status: 'pending', // pending, uploading, success, failed
+          line2Status: 'pending'
+        }
       },
       // 表单校验
       rules: {
@@ -792,6 +874,17 @@ export default {
       },
       // 是否存在最小化窗口
       hasMinimizableDialog: false,
+      // 新增上传相关状态
+      isUploading: false,
+      currentUploadProgress: {
+        total: 0,
+        line1: 0,
+        line2: 0,
+        line1Status: 'pending',
+        line2Status: 'pending'
+      },
+      // 上传任务队列
+      uploadQueue: []
     }
   },
   watch: {
@@ -1047,12 +1140,10 @@ export default {
     //获取第一帧封面
     async getFirstThumbnail(file, form){
       try {
-        const uniqueFileName = `clipped_${Date.now()}_${Math.random().toString(36).slice(2, 8)}.mp4`;
-
         //截取小文件
         const clippedBlob = await this.clipVideoFirstTwoSeconds(file);
 
-        const clippedFile = new File([clippedBlob], uniqueFileName, {
+        const clippedFile = new File([clippedBlob], 'clipped_video.mp4', {
           type: 'video/mp4',
           lastModified: Date.now()
         });
@@ -1128,65 +1219,167 @@ export default {
     //上传腾讯云Pcdn
     async uploadVideoToTxPcdn(file, form, onProgress) {
       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 {
+        // 更新线路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
+      this.isUploading = true;
+      this.form.uploadStatus = 'uploading';
+      
       const file = options.file;
-      this.getMediaDuration(file)
-
-      // 获取第一帧图片
-      await this.getFirstThumbnail(file, this.form);
-
-      // 上传腾讯云pcdn
-      await this.uploadVideoToTxPcdn(file, this.form, options.onProgress);
-
-      // 上传华为obs
-      await this.uploadVideoToHwObs(file, this.form, options.onProgress);
+      this.getMediaDuration(file);
+      
+      // 重置进度
+      this.currentUploadProgress = {
+        total: 0,
+        line1: 0,
+        line2: 0,
+        line1Status: 'pending',
+        line2Status: 'pending'
+      };
 
-      this.form.fileName = file.name;
-      this.form.fileSize = file.size;
+      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.add = false
+        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) {
@@ -1308,7 +1501,7 @@ export default {
       });
     },
 
-    /** 显示上传面板 */
+     /** 显示上传面板 */
     showUploadPanel() {
       this.showUpload = true;
       this.batchUploadForm = {
@@ -1331,7 +1524,6 @@ export default {
     /** 批量上传视频 */
     async batchVideoUpload(options) {
       const file = options.file;
-
       // 创建临时视频对象添加到列表中
       const tempVideo = {
         tempId: Math.random().toString(36).substring(2, 15),
@@ -1348,7 +1540,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 // 保存文件引用
       };
 
@@ -1373,15 +1572,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 || '未知错误'}`);
@@ -1626,6 +1863,345 @@ 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>
@@ -1641,6 +2217,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;